diff options
author | Sven Gothel <[email protected]> | 2020-05-17 09:58:12 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-05-17 09:58:12 +0200 |
commit | 525cb093d2ead1be30cf860d096129a8ec7146bf (patch) | |
tree | 0bf0e063fc9dfbe7b97cf45301dfe1b859b1e5cf /java/jni | |
parent | b12a3e3adf8159a0c252cee67beae35e1b5b879f (diff) |
Working GATT Java Side; GATT Types made fully functional for user to avoid 'technical' GATTHandler
GATT Types made fully functional for user to avoid 'technical' GATTHandler (C++)
> GATTService, GATTCharacteristic, GATTDescriptor
-- Reside in their own respective files
-- Added semantic methods (readValue(), ..) implemented using DBTDevice -> GATTHandler
-- GATTDescriptor owns its value
-- GATTHandler setSendIndicationConfirmation(..) defaults to true
-- Allow user to cirvumvent using GATTHandler manually completely,
device 1--*> services 1--*> characteristics 1--*> descriptor
-- C++ GATT types aligned 1:1 to TinyB (Java)
> Merged GATTIndicationListener + GATTNotificationListener -> GATTCharacteristicListener
-- Simplifying usage, unifying notification and indication
-- Now using a list of shared_ptr<GATTCharacteristicListener> allowing multiple actors
instead of just a one shot entry. Similar to AdapterStatusListener,
we utilize this also on the Java side to implement the TinyB notifications.
See dbt_scanner00.cpp: Simplified high-level usage.
See dbt_scanner01.cpp: Lower-level technical usage w/ GATTHandler.
+++
> Simplified more names
> Removed redundancy in listener callbacks,
-- don't pass adapter when device is already given.
device <*--1> adapter
-- don't pass GATT handle explicitly when characteristic is passed
> Comparison of all GATT types are done by their respective unique handle
Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle)
> GATTHandler: Own L2CAPComm instance directly, instead of shared_ptr.
> JNIMem: Added JNICriticalArray class for RAII style release
++++
++++
Working GATT Java Side
> All toString() methods return the C++ toString() via JNI for better representation.
> DBTDevice.java/JNI: Resolved the odd 'adapter' reference issue:
-- Was not passing the jobject of DBTAdapter, but its shared container refeference ;-)
> All GATT types receive their GATT handler for equal test and identity @ ctor
> GATT read/write Value update the cached value as well as issue the notifyValue on change,
including GATTCharacteristic notification/indication listener
Diffstat (limited to 'java/jni')
-rw-r--r-- | java/jni/JNIMem.hpp | 85 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTAdapter.cxx | 171 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTDevice.cxx | 220 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTGattCharacteristic.cxx | 142 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTGattDescriptor.cxx | 81 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTGattService.cxx | 36 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTManager.cxx | 8 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTNativeDownlink.cxx | 2 |
8 files changed, 628 insertions, 117 deletions
diff --git a/java/jni/JNIMem.hpp b/java/jni/JNIMem.hpp index fee3348b..a6a1dbb4 100644 --- a/java/jni/JNIMem.hpp +++ b/java/jni/JNIMem.hpp @@ -67,8 +67,10 @@ public: extern thread_local JNIEnvContainer jni_env; /* - * This class provides a lifetime-managed GlobalRef variable, which is automatically - * deleted when it goes out of scope. + * This class provides a lifetime-managed GlobalRef variable, + * which is automatically deleted when it goes out of scope. + * + * RAII-style acquire and relinquish via destructor */ class JNIGlobalRef { private: @@ -110,5 +112,84 @@ public: { return !( *this == rhs ); } }; +/* + * This class provides a lifetime-managed 'PrimitiveArrayCritical' pinned heap, + * which is automatically released when it goes out of scope. + * + * RAII-style acquire and relinquish via destructor + */ +template <typename T> +class JNICriticalArray { +public: + enum Mode : jint { + /** Like default 0: If 'isCopy': Update the java array data with the copy and free the copy. */ + UPDATE_AND_RELEASE = 0, + + /** Like JNI_COMMIT: If 'isCopy': Update the java array data with the copy, but do not free the copy. */ + UPDATE_NO_RELEASE = JNI_COMMIT, + + /** Like default JNI_ABORT: If 'isCopy': Do not update the java array data with the copy, but free the copy. */ + NO_UPDATE_AND_RELEASE = JNI_ABORT, + }; + +private: + JNIEnv *env; + Mode mode = UPDATE_AND_RELEASE; + jbyteArray jarray = nullptr; + T* narray = nullptr; + jboolean isCopy = false; + +public: + JNICriticalArray(JNIEnv *env) : env(env) {} + + JNICriticalArray(const JNICriticalArray &o) = delete; + JNICriticalArray(JNICriticalArray &&o) = delete; + JNICriticalArray& operator=(const JNICriticalArray &o) = delete; + JNICriticalArray& operator=(JNICriticalArray &&o) = delete; + + /** + * Release the acquired primitive array, RAII style. + */ + ~JNICriticalArray() { + release(); + } + + /** + * Manual release of the acquired primitive array, + * usually one likes to simply do this via the destructor, RAII style. + */ + void release() { + if( nullptr != narray ) { + env->ReleasePrimitiveArrayCritical(jarray, narray, mode); + this->jarray = nullptr; + this->narray = nullptr; + this->env = nullptr; + } + } + + /** + * Acquired the primitive array. + */ + T* get(jbyteArray jarray, Mode mode=UPDATE_AND_RELEASE) { + if( nullptr == jarray ) { + return nullptr; + } + T* narray = static_cast<T*>( env->GetPrimitiveArrayCritical(jarray, &isCopy) ); + if( nullptr != narray ) { + this->mode = mode; + this->jarray = jarray; + this->narray = narray; + return narray; + } + return nullptr; + } + + /** + * Returns true if the primitive array had been acquired + * and the JVM utilizes a copy of the underlying java array. + */ + bool getIsCopy() const { return isCopy; } +}; + #endif /* JNIMEM__HPP_ */ diff --git a/java/jni/direct_bt/DBTAdapter.cxx b/java/jni/direct_bt/DBTAdapter.cxx index 581ea15d..168a407e 100644 --- a/java/jni/direct_bt/DBTAdapter.cxx +++ b/java/jni/direct_bt/DBTAdapter.cxx @@ -25,7 +25,7 @@ #include "direct_bt_tinyb_DBTAdapter.h" -#define VERBOSE_ON 1 +// #define VERBOSE_ON 1 #include <dbt_debug.hpp> #include "JNIMem.hpp" @@ -42,10 +42,10 @@ static const std::string _eirDataTypeSetClassName("org/tinyb/EIRDataTypeSet"); static const std::string _eirDataTypeSetClazzCtorArgs("(I)V"); static const std::string _deviceClazzCtorArgs("(JLdirect_bt/tinyb/DBTAdapter;Ljava/lang/String;Ljava/lang/String;J)V"); static const std::string _adapterSettingsChangedMethodArgs("(Lorg/tinyb/BluetoothAdapter;Lorg/tinyb/AdapterSettings;Lorg/tinyb/AdapterSettings;Lorg/tinyb/AdapterSettings;J)V"); -static const std::string _deviceStatusMethodArgs("(Lorg/tinyb/BluetoothAdapter;Lorg/tinyb/BluetoothDevice;J)V"); -static const std::string _deviceStatusUpdateMethodArgs("(Lorg/tinyb/BluetoothAdapter;Lorg/tinyb/BluetoothDevice;JLorg/tinyb/EIRDataTypeSet;)V"); +static const std::string _deviceStatusMethodArgs("(Lorg/tinyb/BluetoothDevice;J)V"); +static const std::string _deviceStatusUpdateMethodArgs("(Lorg/tinyb/BluetoothDevice;JLorg/tinyb/EIRDataTypeSet;)V"); -class AdapterStatusCallbackListener : public DBTAdapterStatusListener { +class JNIAdapterStatusListener : public AdapterStatusListener { private: /** package org.tinyb; @@ -54,12 +54,13 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { public void adapterSettingsChanged(final BluetoothAdapter adapter, final AdapterSetting oldmask, final AdapterSetting newmask, final AdapterSetting changedmask, final long timestamp); - public void deviceFound(final BluetoothAdapter adapter, final BluetoothDevice device, final long timestamp); - public void deviceUpdated(final BluetoothAdapter adapter, final BluetoothDevice device, final long timestamp, final EIRDataType updateMask); - public void deviceConnected(final BluetoothAdapter adapter, final BluetoothDevice device, final long timestamp); - public void deviceDisconnected(final BluetoothAdapter adapter, final BluetoothDevice device, final long timestamp); + public void deviceFound(final BluetoothDevice device, final long timestamp); + public void deviceUpdated(final BluetoothDevice device, final long timestamp, final EIRDataType updateMask); + public void deviceConnected(final BluetoothDevice device, final long timestamp); + public void deviceDisconnected(final BluetoothDevice device, final long timestamp); }; */ + const DBTDevice * deviceMatchRef; std::shared_ptr<JavaAnonObj> adapterObjRef; std::unique_ptr<JNIGlobalRef> adapterSettingsClazzRef; jmethodID adapterSettingsClazzCtor; @@ -69,7 +70,6 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { jmethodID deviceClazzCtor; jfieldID deviceClazzTSUpdateField; std::unique_ptr<JNIGlobalRef> listenerObjRef; - std::unique_ptr<JNIGlobalRef> listenerClazzRef; jmethodID mAdapterSettingsChanged = nullptr; jmethodID mDeviceFound = nullptr; jmethodID mDeviceUpdated = nullptr; @@ -78,10 +78,19 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { public: - AdapterStatusCallbackListener(JNIEnv *env, DBTAdapter *adapter, jobject statusListener) { + JNIAdapterStatusListener(JNIEnv *env, DBTAdapter *adapter, jobject statusListener, const DBTDevice * deviceMatchRef) { adapterObjRef = adapter->getJavaObject(); JavaGlobalObj::check(adapterObjRef, E_FILE_LINE); + listenerObjRef = std::unique_ptr<JNIGlobalRef>(new JNIGlobalRef(statusListener)); + jclass listenerClazz = search_class(env, listenerObjRef->getObject()); + java_exception_check_and_throw(env, E_FILE_LINE); + if( nullptr == listenerClazz ) { + throw InternalError("AdapterStatusListener not found", E_FILE_LINE); + } + + this->deviceMatchRef = deviceMatchRef; + // adapterSettingsClazzRef, adapterSettingsClazzCtor { jclass adapterSettingsClazz = search_class(env, _adapterSettingsClassName.c_str()); @@ -135,45 +144,40 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { throw InternalError("DBTDevice::java_class field not found: "+DBTDevice::java_class()+".ts_update", E_FILE_LINE); } - listenerObjRef = std::unique_ptr<JNIGlobalRef>(new JNIGlobalRef(statusListener)); - { - jclass listenerClazz = search_class(env, listenerObjRef->getObject()); - java_exception_check_and_throw(env, E_FILE_LINE); - if( nullptr == listenerClazz ) { - throw InternalError("AdapterStatusListener not found", E_FILE_LINE); - } - listenerClazzRef = std::unique_ptr<JNIGlobalRef>(new JNIGlobalRef(listenerClazz)); - env->DeleteLocalRef(listenerClazz); - } - - - mAdapterSettingsChanged = search_method(env, listenerClazzRef->getClass(), "adapterSettingsChanged", _adapterSettingsChangedMethodArgs.c_str(), false); + mAdapterSettingsChanged = search_method(env, listenerClazz, "adapterSettingsChanged", _adapterSettingsChangedMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == mAdapterSettingsChanged ) { throw InternalError("AdapterStatusListener has no adapterSettingsChanged"+_adapterSettingsChangedMethodArgs+" method, for "+adapter->toString(), E_FILE_LINE); } - mDeviceFound = search_method(env, listenerClazzRef->getClass(), "deviceFound", _deviceStatusMethodArgs.c_str(), false); + mDeviceFound = search_method(env, listenerClazz, "deviceFound", _deviceStatusMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == mDeviceFound ) { throw InternalError("AdapterStatusListener has no deviceFound"+_deviceStatusMethodArgs+" method, for "+adapter->toString(), E_FILE_LINE); } - mDeviceUpdated = search_method(env, listenerClazzRef->getClass(), "deviceUpdated", _deviceStatusUpdateMethodArgs.c_str(), false); + mDeviceUpdated = search_method(env, listenerClazz, "deviceUpdated", _deviceStatusUpdateMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == mDeviceUpdated ) { throw InternalError("AdapterStatusListener has no deviceUpdated"+_deviceStatusMethodArgs+" method, for "+adapter->toString(), E_FILE_LINE); } - mDeviceConnected = search_method(env, listenerClazzRef->getClass(), "deviceConnected", _deviceStatusMethodArgs.c_str(), false); + mDeviceConnected = search_method(env, listenerClazz, "deviceConnected", _deviceStatusMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == mDeviceConnected ) { throw InternalError("AdapterStatusListener has no deviceConnected"+_deviceStatusMethodArgs+" method, for "+adapter->toString(), E_FILE_LINE); } - mDeviceDisconnected = search_method(env, listenerClazzRef->getClass(), "deviceDisconnected", _deviceStatusMethodArgs.c_str(), false); + mDeviceDisconnected = search_method(env, listenerClazz, "deviceDisconnected", _deviceStatusMethodArgs.c_str(), false); java_exception_check_and_throw(env, E_FILE_LINE); if( nullptr == mDeviceDisconnected ) { throw InternalError("AdapterStatusListener has no deviceDisconnected"+_deviceStatusMethodArgs+" method, for "+adapter->toString(), E_FILE_LINE); } } + bool matchDevice(const DBTDevice & device) override { + if( nullptr == deviceMatchRef ) { + return true; + } + return device == *deviceMatchRef; + } + void adapterSettingsChanged(DBTAdapter const &a, const AdapterSetting oldmask, const AdapterSetting newmask, const AdapterSetting changedmask, const uint64_t timestamp) override { JNIEnv *env = *jni_env; @@ -188,34 +192,33 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { #endif (void)a; jobject adapterSettingOld = env->NewObject(adapterSettingsClazzRef->getClass(), adapterSettingsClazzCtor, (jint)oldmask); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(adapterSettingOld, E_FILE_LINE); jobject adapterSettingNew = env->NewObject(adapterSettingsClazzRef->getClass(), adapterSettingsClazzCtor, (jint)newmask); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(adapterSettingNew, E_FILE_LINE); jobject adapterSettingChanged = env->NewObject(adapterSettingsClazzRef->getClass(), adapterSettingsClazzCtor, (jint)changedmask); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(adapterSettingChanged, E_FILE_LINE); env->CallVoidMethod(listenerObjRef->getObject(), mAdapterSettingsChanged, JavaGlobalObj::GetObject(adapterObjRef), adapterSettingOld, adapterSettingNew, adapterSettingChanged, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); } catch(...) { rethrow_and_raise_java_exception(env); } } - void deviceFound(DBTAdapter const &a, std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { + void deviceFound(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { JNIEnv *env = *jni_env; try { #ifdef VERBOSE_ON fprintf(stderr, "****** Native Adapter Device FOUND__: %s\n", device->toString().c_str()); fprintf(stderr, "Status DBTAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); + fprintf(stderr, "%s\n", device->getAdapter().toString().c_str()); #endif - (void)a; jobject jdevice; std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); if( JavaGlobalObj::isValid(jDeviceRef) ) { @@ -226,98 +229,91 @@ class AdapterStatusCallbackListener : public DBTAdapterStatusListener { // Device(final long nativeInstance, final Adapter adptr, final String address, final String name) const jstring addr = from_string_to_jstring(env, device->getAddressString()); const jstring name = from_string_to_jstring(env, device->getName()); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); jobject jDevice = env->NewObject(deviceClazzRef->getClass(), deviceClazzCtor, - (jlong)device.get(), adapterObjRef, addr, name, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + (jlong)device.get(), JavaGlobalObj::GetObject(adapterObjRef), addr, name, (jlong)timestamp); + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(jDevice, E_FILE_LINE); std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); JavaGlobalObj::check(jDeviceRef, E_FILE_LINE); jdevice = JavaGlobalObj::GetObject(jDeviceRef); } - env->CallVoidMethod(listenerObjRef->getObject(), mDeviceFound, - JavaGlobalObj::GetObject(adapterObjRef), jdevice, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + env->CallVoidMethod(listenerObjRef->getObject(), mDeviceFound, jdevice, (jlong)timestamp); + java_exception_check_and_throw(env, E_FILE_LINE); } catch(...) { rethrow_and_raise_java_exception(env); } } - void deviceUpdated(DBTAdapter const &a, std::shared_ptr<DBTDevice> device, const uint64_t timestamp, const EIRDataType updateMask) override { + void deviceUpdated(std::shared_ptr<DBTDevice> device, const uint64_t timestamp, const EIRDataType updateMask) override { JNIEnv *env = *jni_env; try { #ifdef VERBOSE_ON fprintf(stderr, "****** Native Adapter Device UPDATED: %s of %s\n", direct_bt::eirDataMaskToString(updateMask).c_str(), device->toString().c_str()); fprintf(stderr, "Status DBTAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); + fprintf(stderr, "%s\n", device->getAdapter().toString().c_str()); #endif - (void)a; std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); JavaGlobalObj::check(jDeviceRef, E_FILE_LINE); env->SetLongField(JavaGlobalObj::GetObject(jDeviceRef), deviceClazzTSUpdateField, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); jobject eirDataTypeSet = env->NewObject(eirDataTypeSetClazzRef->getClass(), eirDataTypeSetClazzCtor, (jint)updateMask); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(eirDataTypeSet, E_FILE_LINE); - env->CallVoidMethod(listenerObjRef->getObject(), mDeviceUpdated, - JavaGlobalObj::GetObject(adapterObjRef), JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp, eirDataTypeSet); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + env->CallVoidMethod(listenerObjRef->getObject(), mDeviceUpdated, JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp, eirDataTypeSet); + java_exception_check_and_throw(env, E_FILE_LINE); } catch(...) { rethrow_and_raise_java_exception(env); } } - void deviceConnected(DBTAdapter const &a, std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { + void deviceConnected(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { JNIEnv *env = *jni_env; try { #ifdef VERBOSE_ON fprintf(stderr, "****** DBTAdapter Device CONNECTED: %s\n", device->toString().c_str()); fprintf(stderr, "Status DBTAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); + fprintf(stderr, "%s\n", device->getAdapter().toString().c_str()); #endif - (void)a; std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); JavaGlobalObj::check(jDeviceRef, E_FILE_LINE); env->SetLongField(JavaGlobalObj::GetObject(jDeviceRef), deviceClazzTSUpdateField, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } - env->CallVoidMethod(listenerObjRef->getObject(), mDeviceConnected, - JavaGlobalObj::GetObject(adapterObjRef), JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); + env->CallVoidMethod(listenerObjRef->getObject(), mDeviceConnected, JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp); + java_exception_check_and_throw(env, E_FILE_LINE); } catch(...) { rethrow_and_raise_java_exception(env); } } - void deviceDisconnected(DBTAdapter const &a, std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { + void deviceDisconnected(std::shared_ptr<DBTDevice> device, const uint64_t timestamp) override { JNIEnv *env = *jni_env; try { #ifdef VERBOSE_ON fprintf(stderr, "****** DBTAdapter Device DISCONNECTED: %s\n", device->toString().c_str()); fprintf(stderr, "Status DBTAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); + fprintf(stderr, "%s\n", device->getAdapter().toString().c_str()); #endif - (void)a; std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); JavaGlobalObj::check(jDeviceRef, E_FILE_LINE); env->SetLongField(JavaGlobalObj::GetObject(jDeviceRef), deviceClazzTSUpdateField, (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } - env->CallVoidMethod(listenerObjRef->getObject(), mDeviceDisconnected, - JavaGlobalObj::GetObject(adapterObjRef), JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); + env->CallVoidMethod(listenerObjRef->getObject(), mDeviceDisconnected, JavaGlobalObj::GetObject(jDeviceRef), (jlong)timestamp); + java_exception_check_and_throw(env, E_FILE_LINE); } catch(...) { rethrow_and_raise_java_exception(env); } } }; -jboolean Java_direct_1bt_tinyb_DBTAdapter_addStatusListener(JNIEnv *env, jobject obj, jobject statusListener) +jboolean Java_direct_1bt_tinyb_DBTAdapter_addStatusListener(JNIEnv *env, jobject obj, jobject statusListener, jobject jdeviceMatch) { try { if( nullptr == statusListener ) { throw IllegalArgumentException("statusListener is null", E_FILE_LINE); } { - AdapterStatusCallbackListener * pre = - getObjectRef<AdapterStatusCallbackListener>(env, statusListener, "nativeInstance"); + JNIAdapterStatusListener * pre = + getObjectRef<JNIAdapterStatusListener>(env, statusListener, "nativeInstance"); if( nullptr != pre ) { WARN_PRINT("statusListener's nativeInstance not null, already in use"); return false; @@ -326,8 +322,14 @@ jboolean Java_direct_1bt_tinyb_DBTAdapter_addStatusListener(JNIEnv *env, jobject DBTAdapter *adapter = getInstance<DBTAdapter>(env, obj); JavaGlobalObj::check(adapter->getJavaObject(), E_FILE_LINE); - std::shared_ptr<DBTAdapterStatusListener> l = - std::shared_ptr<DBTAdapterStatusListener>( new AdapterStatusCallbackListener(env, adapter, statusListener) ); + DBTDevice * deviceMatchRef = nullptr; + if( nullptr != jdeviceMatch ) { + deviceMatchRef = getInstance<DBTDevice>(env, jdeviceMatch); + JavaGlobalObj::check(deviceMatchRef->getJavaObject(), E_FILE_LINE); + } + + std::shared_ptr<AdapterStatusListener> l = + std::shared_ptr<AdapterStatusListener>( new JNIAdapterStatusListener(env, adapter, statusListener, deviceMatchRef) ); if( adapter->addStatusListener( l ) ) { setInstance(env, statusListener, l.get()); @@ -345,20 +347,20 @@ jboolean Java_direct_1bt_tinyb_DBTAdapter_removeStatusListener(JNIEnv *env, jobj if( nullptr == statusListener ) { throw IllegalArgumentException("statusListener is null", E_FILE_LINE); } - const AdapterStatusCallbackListener * pre = - getObjectRef<AdapterStatusCallbackListener>(env, statusListener, "nativeInstance"); + const JNIAdapterStatusListener * pre = + getObjectRef<JNIAdapterStatusListener>(env, statusListener, "nativeInstance"); if( nullptr == pre ) { WARN_PRINT("statusListener's nativeInstance is null, not in use"); return false; } - setObjectRef<AdapterStatusCallbackListener>(env, statusListener, nullptr, "nativeInstance"); + setObjectRef<JNIAdapterStatusListener>(env, statusListener, nullptr, "nativeInstance"); DBTAdapter *adapter = getInstance<DBTAdapter>(env, obj); JavaGlobalObj::check(adapter->getJavaObject(), E_FILE_LINE); - // set our callback discovery listener. if( ! adapter->removeStatusListener( pre ) ) { - throw InternalError("Failed to remove statusListener with nativeInstance", E_FILE_LINE); + WARN_PRINT("Failed to remove statusListener with nativeInstance: %p at %s", pre, adapter->toString().c_str()); + return false; } return true; } catch(...) { @@ -367,6 +369,29 @@ jboolean Java_direct_1bt_tinyb_DBTAdapter_removeStatusListener(JNIEnv *env, jobj return JNI_FALSE; } +jint Java_direct_1bt_tinyb_DBTAdapter_removeAllStatusListener(JNIEnv *env, jobject obj) { + try { + DBTAdapter *adapter = getInstance<DBTAdapter>(env, obj); + JavaGlobalObj::check(adapter->getJavaObject(), E_FILE_LINE); + + return adapter->removeAllStatusListener(); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return 0; +} + +jstring Java_direct_1bt_tinyb_DBTAdapter_toStringImpl(JNIEnv *env, jobject obj) { + try { + DBTAdapter *nativePtr = getInstance<DBTAdapter>(env, obj); + JavaGlobalObj::check(nativePtr->getJavaObject(), E_FILE_LINE); + return from_string_to_jstring(env, nativePtr->toString()); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + jboolean Java_direct_1bt_tinyb_DBTAdapter_openImpl(JNIEnv *env, jobject obj) { try { DBTAdapter *adapter = getInstance<DBTAdapter>(env, obj); @@ -572,7 +597,7 @@ jobject Java_direct_1bt_tinyb_DBTAdapter_connectDevice(JNIEnv *env, jobject obj, std::shared_ptr<JavaAnonObj> jDeviceRef = device->getJavaObject(); JavaGlobalObj::check(jDeviceRef, E_FILE_LINE); - device->defaultConnect(); + device->connectHCIDefault(); return JavaGlobalObj::GetObject(jDeviceRef); } } catch(...) { diff --git a/java/jni/direct_bt/DBTDevice.cxx b/java/jni/direct_bt/DBTDevice.cxx index 9d5d9e1f..cb249d4b 100644 --- a/java/jni/direct_bt/DBTDevice.cxx +++ b/java/jni/direct_bt/DBTDevice.cxx @@ -24,9 +24,8 @@ */ #include "direct_bt_tinyb_DBTDevice.h" -#include "direct_bt_tinyb_DBTAdapter.h" -#define VERBOSE_ON 1 +// #define VERBOSE_ON 1 #include <dbt_debug.hpp> #include "JNIMem.hpp" @@ -38,6 +37,113 @@ using namespace direct_bt; +static const std::string _notificationReceivedMethodArgs("(Lorg/tinyb/BluetoothGattCharacteristic;[BJ)V"); +static const std::string _indicationReceivedMethodArgs("(Lorg/tinyb/BluetoothGattCharacteristic;[BJZ)V"); + +class JNICharacteristicListener : public GATTCharacteristicListener { + private: + /** + package org.tinyb; + + public abstract class GATTCharacteristicListener { + long nativeInstance; + + public void notificationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp) { + } + + public void indicationReceived(final BluetoothGattCharacteristic charDecl, + final byte[] value, final long timestamp, + final boolean confirmationSent) { + } + + }; + */ + const GATTCharacteristic * characteristicMatchRef; + std::shared_ptr<JavaAnonObj> deviceObjRef; + std::unique_ptr<JNIGlobalRef> listenerObjRef; + jmethodID mNotificationReceived = nullptr; + jmethodID mIndicationReceived = nullptr; + + public: + + JNICharacteristicListener(JNIEnv *env, DBTDevice *device, jobject listener, const GATTCharacteristic * characteristicMatchRef) { + deviceObjRef = device->getJavaObject(); + JavaGlobalObj::check(deviceObjRef, E_FILE_LINE); + + listenerObjRef = std::unique_ptr<JNIGlobalRef>(new JNIGlobalRef(listener)); + jclass listenerClazz = search_class(env, listenerObjRef->getObject()); + java_exception_check_and_throw(env, E_FILE_LINE); + if( nullptr == listenerClazz ) { + throw InternalError("CharacteristicListener not found", E_FILE_LINE); + } + + this->characteristicMatchRef = characteristicMatchRef; + + mNotificationReceived = search_method(env, listenerClazz, "notificationReceived", _notificationReceivedMethodArgs.c_str(), false); + java_exception_check_and_throw(env, E_FILE_LINE); + if( nullptr == mNotificationReceived ) { + throw InternalError("GATTCharacteristicListener has no notificationReceived"+_notificationReceivedMethodArgs+" method, for "+device->toString(), E_FILE_LINE); + } + mIndicationReceived = search_method(env, listenerClazz, "indicationReceived", _indicationReceivedMethodArgs.c_str(), false); + java_exception_check_and_throw(env, E_FILE_LINE); + if( nullptr == mNotificationReceived ) { + throw InternalError("GATTCharacteristicListener has no indicationReceived"+_indicationReceivedMethodArgs+" method, for "+device->toString(), E_FILE_LINE); + } + } + + bool match(const GATTCharacteristic & characteristic) override { + if( nullptr == characteristicMatchRef ) { + return true; + } + return characteristic == *characteristicMatchRef; + } + + void notificationReceived(GATTCharacteristicRef charDecl, + std::shared_ptr<TROOctets> charValue, const uint64_t timestamp) override { + JNIEnv *env = *jni_env; + try { + JavaGlobalObj::check(charDecl->getJavaObject(), E_FILE_LINE); + jobject jCharDecl = JavaGlobalObj::GetObject(charDecl->getJavaObject()); + + const size_t value_size = charValue->getSize(); + jbyteArray jvalue = env->NewByteArray((jsize)value_size); + env->SetByteArrayRegion(jvalue, 0, (jsize)value_size, (const jbyte *)charValue->get_ptr()); + java_exception_check_and_throw(env, E_FILE_LINE); + + + env->CallVoidMethod(listenerObjRef->getObject(), mNotificationReceived, + jCharDecl, jvalue, (jlong)timestamp); + java_exception_check_and_throw(env, E_FILE_LINE); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + } + + void indicationReceived(GATTCharacteristicRef charDecl, + std::shared_ptr<TROOctets> charValue, const uint64_t timestamp, + const bool confirmationSent) override { + JNIEnv *env = *jni_env; + try { + JavaGlobalObj::check(charDecl->getJavaObject(), E_FILE_LINE); + jobject jCharDecl = JavaGlobalObj::GetObject(charDecl->getJavaObject()); + + const size_t value_size = charValue->getSize(); + jbyteArray jvalue = env->NewByteArray((jsize)value_size); + env->SetByteArrayRegion(jvalue, 0, (jsize)value_size, (const jbyte *)charValue->get_ptr()); + java_exception_check_and_throw(env, E_FILE_LINE); + + + env->CallVoidMethod(listenerObjRef->getObject(), mIndicationReceived, + jCharDecl, jvalue, (jlong)timestamp, (jboolean)confirmationSent); + java_exception_check_and_throw(env, E_FILE_LINE); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + } +}; + + void Java_direct_1bt_tinyb_DBTDevice_initImpl(JNIEnv *env, jobject obj) { try { @@ -48,47 +154,107 @@ void Java_direct_1bt_tinyb_DBTDevice_initImpl(JNIEnv *env, jobject obj) } } -jboolean Java_direct_1bt_tinyb_DBTDevice_addStatusListener(JNIEnv *env, jobject obj, jobject statusListener) -{ +jstring Java_direct_1bt_tinyb_DBTDevice_toStringImpl(JNIEnv *env, jobject obj) { try { + DBTDevice *nativePtr = getInstance<DBTDevice>(env, obj); + JavaGlobalObj::check(nativePtr->getJavaObject(), E_FILE_LINE); + return from_string_to_jstring(env, nativePtr->toString()); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +jboolean Java_direct_1bt_tinyb_DBTDevice_addCharacteristicListener(JNIEnv *env, jobject obj, jobject listener, jobject jcharacteristicMatch) { + try { + if( nullptr == listener ) { + throw IllegalArgumentException("characteristicListener is null", E_FILE_LINE); + } + { + JNICharacteristicListener * pre = + getObjectRef<JNICharacteristicListener>(env, listener, "nativeInstance"); + if( nullptr != pre ) { + WARN_PRINT("characteristicListener's nativeInstance not null, already in use"); + return false; + } + } DBTDevice *device = getInstance<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); + if( nullptr == gatt ) { + throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ device->toString(), E_FILE_LINE); + } - DBTAdapter * adapter = const_cast<DBTAdapter *>( &device->getAdapter() ); - std::shared_ptr<JavaAnonObj> adapterObjRef = adapter->getJavaObject(); - JavaGlobalObj::check(adapterObjRef, E_FILE_LINE); - jobject jadapter = JavaGlobalObj::GetObject(adapterObjRef); + GATTCharacteristic * characteristicMatchRef = nullptr; + if( nullptr != jcharacteristicMatch ) { + characteristicMatchRef = getInstance<GATTCharacteristic>(env, jcharacteristicMatch); + JavaGlobalObj::check(characteristicMatchRef->getJavaObject(), E_FILE_LINE); + } - return Java_direct_1bt_tinyb_DBTAdapter_addStatusListener(env, jadapter, statusListener); + std::shared_ptr<GATTCharacteristicListener> l = + std::shared_ptr<GATTCharacteristicListener>( new JNICharacteristicListener(env, device, listener, characteristicMatchRef) ); + + if( gatt->addCharacteristicListener(l) ) { + setInstance(env, listener, l.get()); + return JNI_TRUE; + } } catch(...) { rethrow_and_raise_java_exception(env); } return JNI_FALSE; } -jboolean Java_direct_1bt_tinyb_DBTDevice_removeStatusListener(JNIEnv *env, jobject obj, jobject statusListener) -{ +jboolean Java_direct_1bt_tinyb_DBTDevice_removeCharacteristicListener(JNIEnv *env, jobject obj, jobject statusListener) { try { + if( nullptr == statusListener ) { + throw IllegalArgumentException("characteristicListener is null", E_FILE_LINE); + } + JNICharacteristicListener * pre = + getObjectRef<JNICharacteristicListener>(env, statusListener, "nativeInstance"); + if( nullptr == pre ) { + WARN_PRINT("characteristicListener's nativeInstance is null, not in use"); + return false; + } + setObjectRef<JNICharacteristicListener>(env, statusListener, nullptr, "nativeInstance"); + DBTDevice *device = getInstance<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); + if( nullptr == gatt ) { + throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ device->toString(), E_FILE_LINE); + } - DBTAdapter * adapter = const_cast<DBTAdapter *>( &device->getAdapter() ); - std::shared_ptr<JavaAnonObj> adapterObjRef = adapter->getJavaObject(); - JavaGlobalObj::check(adapterObjRef, E_FILE_LINE); - jobject jadapter = JavaGlobalObj::GetObject(adapterObjRef); - - return Java_direct_1bt_tinyb_DBTAdapter_removeStatusListener(env, jadapter, statusListener); + if( ! gatt->removeCharacteristicListener(pre) ) { + WARN_PRINT("Failed to remove characteristicListener with nativeInstance: %p at %s", pre, device->toString().c_str()); + return false; + } + return true; } catch(...) { rethrow_and_raise_java_exception(env); } return JNI_FALSE; } +jint Java_direct_1bt_tinyb_DBTDevice_removeAllCharacteristicListener(JNIEnv *env, jobject obj) { + try { + DBTDevice *device = getInstance<DBTDevice>(env, obj); + JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); + if( nullptr == gatt ) { + throw IllegalStateException("Characteristic's device GATTHandle not connected: "+ device->toString(), E_FILE_LINE); + } + + return gatt->removeAllCharacteristicListener(); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return 0; +} + void Java_direct_1bt_tinyb_DBTDevice_deleteImpl(JNIEnv *env, jobject obj) { try { DBTDevice *device = getInstance<DBTDevice>(env, obj); - JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); device->remove(); // No delete: DBTDevice instance owned by DBTAdapter } catch(...) { @@ -125,8 +291,8 @@ jboolean Java_direct_1bt_tinyb_DBTDevice_connectImpl(JNIEnv *env, jobject obj) try { DBTDevice *device = getInstance<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); - uint16_t chandle = device->defaultConnect(); - if( 0 == chandle ) { + uint16_t hciHandle = device->connectHCIDefault(); + if( 0 == hciHandle ) { return JNI_FALSE; } std::shared_ptr<GATTHandler> gatt = device->connectGATT(); @@ -158,7 +324,7 @@ jboolean Java_direct_1bt_tinyb_DBTDevice_connectImpl(JNIEnv *env, jobject obj) // getter // -static const std::string _serviceClazzCtorArgs("(JLorg/tinyb/BluetoothDevice;ZLjava/lang/String;)V"); +static const std::string _serviceClazzCtorArgs("(JLdirect_bt/tinyb/DBTDevice;ZLjava/lang/String;SS)V"); jobject Java_direct_1bt_tinyb_DBTDevice_getServices(JNIEnv *env, jobject obj) { try { @@ -168,7 +334,8 @@ jobject Java_direct_1bt_tinyb_DBTDevice_getServices(JNIEnv *env, jobject obj) { std::shared_ptr<GATTHandler> gatt = device->getGATTHandler(); if( nullptr != gatt ) { std::vector<std::shared_ptr<GATTService>> & services = gatt->getServices(); - // DBTGattService(final long nativeInstance, final BluetoothDevice device, final boolean isPrimary, final String uuid) + // DBTGattService(final long nativeInstance, final DBTDevice device, final boolean isPrimary, + // final String type_uuid, final short handleStart, final short handleEnd) std::function<jobject(JNIEnv*, jclass, jmethodID, GATTService*)> ctor_service = [](JNIEnv *env, jclass clazz, jmethodID clazz_ctor, GATTService *service)->jobject { @@ -177,10 +344,11 @@ jobject Java_direct_1bt_tinyb_DBTDevice_getServices(JNIEnv *env, jobject obj) { jobject jdevice = JavaGlobalObj::GetObject(service->device->getJavaObject()); const jboolean isPrimary = service->isPrimary; const jstring uuid = from_string_to_jstring(env, service->type->toString()); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); - jobject jservice = env->NewObject(clazz, clazz_ctor, (jlong)service, jdevice, isPrimary, uuid); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + jobject jservice = env->NewObject(clazz, clazz_ctor, (jlong)service, jdevice, isPrimary, + uuid, service->startHandle, service->endHandle); + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(jservice, E_FILE_LINE); std::shared_ptr<JavaAnonObj> jServiceRef = service->getJavaObject(); JavaGlobalObj::check(jServiceRef, E_FILE_LINE); @@ -188,6 +356,8 @@ jobject Java_direct_1bt_tinyb_DBTDevice_getServices(JNIEnv *env, jobject obj) { return JavaGlobalObj::GetObject(jServiceRef); }; return convert_vector_sharedptr_to_jarraylist<GATTService>(env, services, _serviceClazzCtorArgs.c_str(), ctor_service); + } else { + WARN_PRINT("Device GATTHandle not connected: %s", device->toString().c_str()); } } catch(...) { rethrow_and_raise_java_exception(env); diff --git a/java/jni/direct_bt/DBTGattCharacteristic.cxx b/java/jni/direct_bt/DBTGattCharacteristic.cxx index c19701a3..2222e435 100644 --- a/java/jni/direct_bt/DBTGattCharacteristic.cxx +++ b/java/jni/direct_bt/DBTGattCharacteristic.cxx @@ -25,10 +25,150 @@ #include "direct_bt_tinyb_DBTGattCharacteristic.h" +// #define VERBOSE_ON 1 +#include <dbt_debug.hpp> + #include "JNIMem.hpp" #include "helper_base.hpp" +#include "helper_dbt.hpp" -#include "direct_bt/DBTTypes.hpp" +#include "direct_bt/DBTDevice.hpp" +#include "direct_bt/DBTAdapter.hpp" using namespace direct_bt; +jstring Java_direct_1bt_tinyb_DBTGattCharacteristic_toStringImpl(JNIEnv *env, jobject obj) { + try { + GATTCharacteristic *nativePtr = getInstance<GATTCharacteristic>(env, obj); + JavaGlobalObj::check(nativePtr->getJavaObject(), E_FILE_LINE); + return from_string_to_jstring(env, nativePtr->toString()); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +void Java_direct_1bt_tinyb_DBTGattCharacteristic_deleteImpl(JNIEnv *env, jobject obj) { + try { + GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); + (void)characteristic; + // No delete: Service instance owned by GATTService -> DBTDevice + } catch(...) { + rethrow_and_raise_java_exception(env); + } +} + +static const std::string _descriptorClazzCtorArgs("(JLdirect_bt/tinyb/DBTGattCharacteristic;Ljava/lang/String;S[B)V"); + +jobject Java_direct_1bt_tinyb_DBTGattCharacteristic_getDescriptorsImpl(JNIEnv *env, jobject obj) { + try { + GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); + JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); + + std::vector<GATTDescriptorRef> & descriptorList = characteristic->descriptorList; + + // DBTGattDescriptor(final long nativeInstance, final DBTGattCharacteristic characteristic, + // final String type_uuid, final short handle, final byte[] value) + + // DBTGattDescriptor(final long nativeInstance, final DBTGattCharacteristic characteristic, + // final String type_uuid, final short handle, final byte[] value) + + std::function<jobject(JNIEnv*, jclass, jmethodID, GATTDescriptor *)> ctor_desc = + [](JNIEnv *env, jclass clazz, jmethodID clazz_ctor, GATTDescriptor *descriptor)->jobject { + // prepare adapter ctor + JavaGlobalObj::check(descriptor->characteristic->getJavaObject(), E_FILE_LINE); + jobject jcharacteristic = JavaGlobalObj::GetObject(descriptor->characteristic->getJavaObject()); + + const jstring uuid = from_string_to_jstring(env, descriptor->type->toString()); + java_exception_check_and_throw(env, E_FILE_LINE); + + const size_t value_size = descriptor->value.getSize(); + jbyteArray jvalue = env->NewByteArray((jsize)value_size); + env->SetByteArrayRegion(jvalue, 0, (jsize)value_size, (const jbyte *)descriptor->value.get_ptr()); + java_exception_check_and_throw(env, E_FILE_LINE); + + jobject jchar = env->NewObject(clazz, clazz_ctor, (jlong)descriptor, jcharacteristic, + uuid, (jshort)descriptor->handle, jvalue); + java_exception_check_and_throw(env, E_FILE_LINE); + JNIGlobalRef::check(jchar, E_FILE_LINE); + std::shared_ptr<JavaAnonObj> jCharRef = descriptor->getJavaObject(); + JavaGlobalObj::check(jCharRef, E_FILE_LINE); + + return JavaGlobalObj::GetObject(jCharRef); + }; + return convert_vector_sharedptr_to_jarraylist<GATTDescriptor>(env, descriptorList, _descriptorClazzCtorArgs.c_str(), ctor_desc); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +jbyteArray Java_direct_1bt_tinyb_DBTGattCharacteristic_readValueImpl(JNIEnv *env, jobject obj) { + try { + GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); + JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); + + POctets res(GATTHandler::ClientMaxMTU, 0); + if( !characteristic->readValue(res) ) { + ERR_PRINT("Characteristic readValue failed: %s", characteristic->toString().c_str()); + return JNI_FALSE; + } + + const size_t value_size = res.getSize(); + jbyteArray jres = env->NewByteArray((jsize)value_size); + env->SetByteArrayRegion(jres, 0, (jsize)value_size, (const jbyte *)res.get_ptr()); + java_exception_check_and_throw(env, E_FILE_LINE); + return jres; + + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_writeValueImpl(JNIEnv *env, jobject obj, jbyteArray jvalue) { + try { + if( nullptr == jvalue ) { + throw IllegalArgumentException("byte array null", E_FILE_LINE); + } + const int value_size = env->GetArrayLength(jvalue); + if( 0 == value_size ) { + return JNI_TRUE; + } + GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); + JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); + + JNICriticalArray<uint8_t> criticalArray(env); // RAII - release + uint8_t * value_ptr = criticalArray.get(jvalue, criticalArray.Mode::NO_UPDATE_AND_RELEASE); + if( NULL == value_ptr ) { + throw InternalError("GetPrimitiveArrayCritical(byte array) is null", E_FILE_LINE); + } + TROOctets value(value_ptr, value_size); + if( !characteristic->writeValue(value) ) { + ERR_PRINT("Characteristic writeValue failed: %s", characteristic->toString().c_str()); + return JNI_FALSE; + } + return JNI_TRUE; + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return JNI_FALSE; +} + +jboolean Java_direct_1bt_tinyb_DBTGattCharacteristic_enableValueNotificationsImpl(JNIEnv *env, jobject obj, jboolean enable) { + try { + GATTCharacteristic *characteristic = getInstance<GATTCharacteristic>(env, obj); + JavaGlobalObj::check(characteristic->getJavaObject(), E_FILE_LINE); + + bool cccdEnableResult[2]; + bool res = characteristic->configIndicationNotification(enable, enable, cccdEnableResult); + DBG_PRINT("DBTGattCharacteristic::configIndicationNotification Config Notification(%d), Indication(%d): Result %d", + cccdEnableResult[0], cccdEnableResult[1], res); + (void) cccdEnableResult; + return res; + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return JNI_FALSE; +} + diff --git a/java/jni/direct_bt/DBTGattDescriptor.cxx b/java/jni/direct_bt/DBTGattDescriptor.cxx index d90c559b..2a3443fc 100644 --- a/java/jni/direct_bt/DBTGattDescriptor.cxx +++ b/java/jni/direct_bt/DBTGattDescriptor.cxx @@ -25,10 +25,89 @@ #include "direct_bt_tinyb_DBTGattDescriptor.h" +// #define VERBOSE_ON 1 +#include <dbt_debug.hpp> + #include "JNIMem.hpp" #include "helper_base.hpp" +#include "helper_dbt.hpp" -#include "direct_bt/DBTTypes.hpp" +#include "direct_bt/DBTDevice.hpp" +#include "direct_bt/DBTAdapter.hpp" using namespace direct_bt; +void Java_direct_1bt_tinyb_DBTGattDescriptor_deleteImpl(JNIEnv *env, jobject obj) { + try { + GATTDescriptor *descriptor = getInstance<GATTDescriptor>(env, obj); + (void)descriptor; + // No delete: Service instance owned by GATTService -> DBTDevice + } catch(...) { + rethrow_and_raise_java_exception(env); + } +} + +jstring Java_direct_1bt_tinyb_DBTGattDescriptor_toStringImpl(JNIEnv *env, jobject obj) { + try { + GATTDescriptor *nativePtr = getInstance<GATTDescriptor>(env, obj); + JavaGlobalObj::check(nativePtr->getJavaObject(), E_FILE_LINE); + return from_string_to_jstring(env, nativePtr->toString()); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +jbyteArray Java_direct_1bt_tinyb_DBTGattDescriptor_readValueImpl(JNIEnv *env, jobject obj) { + try { + GATTDescriptor *descriptor = getInstance<GATTDescriptor>(env, obj); + JavaGlobalObj::check(descriptor->getJavaObject(), E_FILE_LINE); + + if( !descriptor->readValue() ) { + ERR_PRINT("Characteristic readValue failed: %s", descriptor->toString().c_str()); + return JNI_FALSE; + } + const size_t value_size = descriptor->value.getSize(); + jbyteArray jres = env->NewByteArray((jsize)value_size); + env->SetByteArrayRegion(jres, 0, (jsize)value_size, (const jbyte *)descriptor->value.get_ptr()); + java_exception_check_and_throw(env, E_FILE_LINE); + return jres; + + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + +jboolean Java_direct_1bt_tinyb_DBTGattDescriptor_writeValueImpl(JNIEnv *env, jobject obj, jbyteArray jvalue) { + try { + if( nullptr == jvalue ) { + throw IllegalArgumentException("byte array null", E_FILE_LINE); + } + const int value_size = env->GetArrayLength(jvalue); + if( 0 == value_size ) { + return JNI_TRUE; + } + GATTDescriptor *descriptor = getInstance<GATTDescriptor>(env, obj); + JavaGlobalObj::check(descriptor->getJavaObject(), E_FILE_LINE); + + JNICriticalArray<uint8_t> criticalArray(env); // RAII - release + uint8_t * value_ptr = criticalArray.get(jvalue, criticalArray.Mode::NO_UPDATE_AND_RELEASE); + if( NULL == value_ptr ) { + throw InternalError("GetPrimitiveArrayCritical(byte array) is null", E_FILE_LINE); + } + TROOctets value(value_ptr, value_size); + descriptor->value = value; // copy data + + if( !descriptor->writeValue() ) { + ERR_PRINT("Descriptor writeValue failed: %s", descriptor->toString().c_str()); + return JNI_FALSE; + } + return JNI_TRUE; + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return JNI_FALSE; +} + + diff --git a/java/jni/direct_bt/DBTGattService.cxx b/java/jni/direct_bt/DBTGattService.cxx index 3361a44c..6b3668e1 100644 --- a/java/jni/direct_bt/DBTGattService.cxx +++ b/java/jni/direct_bt/DBTGattService.cxx @@ -25,7 +25,7 @@ #include "direct_bt_tinyb_DBTGattService.h" -#define VERBOSE_ON 1 +// #define VERBOSE_ON 1 #include <dbt_debug.hpp> #include "JNIMem.hpp" @@ -37,10 +37,21 @@ using namespace direct_bt; +jstring Java_direct_1bt_tinyb_DBTGattService_toStringImpl(JNIEnv *env, jobject obj) { + try { + GATTService *nativePtr = getInstance<GATTService>(env, obj); + JavaGlobalObj::check(nativePtr->getJavaObject(), E_FILE_LINE); + return from_string_to_jstring(env, nativePtr->toString()); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return nullptr; +} + + void Java_direct_1bt_tinyb_DBTGattService_deleteImpl(JNIEnv *env, jobject obj) { try { GATTService *service = getInstance<GATTService>(env, obj); - JavaGlobalObj::check(service->getJavaObject(), E_FILE_LINE); (void)service; // No delete: Service instance owned by DBTDevice } catch(...) { @@ -48,16 +59,19 @@ void Java_direct_1bt_tinyb_DBTGattService_deleteImpl(JNIEnv *env, jobject obj) { } } -static const std::string _characteristicClazzCtorArgs("(JLorg/tinyb/BluetoothGattService;[Ljava/lang/String;Ljava/lang/String;)V"); +static const std::string _characteristicClazzCtorArgs("(JLdirect_bt/tinyb/DBTGattService;S[Ljava/lang/String;Ljava/lang/String;SI)V"); -jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristics(JNIEnv *env, jobject obj) { +jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristicsImpl(JNIEnv *env, jobject obj) { try { GATTService *service = getInstance<GATTService>(env, obj); JavaGlobalObj::check(service->getJavaObject(), E_FILE_LINE); std::vector<std::shared_ptr<GATTCharacteristic>> & characteristics = service->characteristicList; - // DBTGattCharacteristic(final long nativeInstance, final BluetoothGattService service, final String[] properties, final String uuid) + // DBTGattCharacteristic(final long nativeInstance, final DBTGattService service, + // final short handle, final String[] properties, + // final String value_type_uuid, final short value_handle, + // final int clientCharacteristicsConfigIndex) std::function<jobject(JNIEnv*, jclass, jmethodID, GATTCharacteristic *)> ctor_char = [](JNIEnv *env, jclass clazz, jmethodID clazz_ctor, GATTCharacteristic *characteristic)->jobject { @@ -70,19 +84,21 @@ jobject Java_direct_1bt_tinyb_DBTGattService_getCharacteristics(JNIEnv *env, job jclass string_class = search_class(env, "Ljava/lang/String;"); jobjectArray jproperties = env->NewObjectArray(props_size, string_class, 0); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); for (unsigned int i = 0; i < props_size; ++i) { jobject elem = from_string_to_jstring(env, *props[i].get()); env->SetObjectArrayElement(jproperties, i, elem); } - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); const jstring uuid = from_string_to_jstring(env, characteristic->value_type->toString()); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); - jobject jchar = env->NewObject(clazz, clazz_ctor, (jlong)characteristic, jservice, jproperties, uuid); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + jobject jchar = env->NewObject(clazz, clazz_ctor, (jlong)characteristic, jservice, + characteristic->handle, jproperties, + uuid, characteristic->value_handle, characteristic->clientCharacteristicsConfigIndex); + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(jchar, E_FILE_LINE); std::shared_ptr<JavaAnonObj> jCharRef = characteristic->getJavaObject(); JavaGlobalObj::check(jCharRef, E_FILE_LINE); diff --git a/java/jni/direct_bt/DBTManager.cxx b/java/jni/direct_bt/DBTManager.cxx index 6e59b1fb..d9fc55bf 100644 --- a/java/jni/direct_bt/DBTManager.cxx +++ b/java/jni/direct_bt/DBTManager.cxx @@ -25,7 +25,7 @@ #include "direct_bt_tinyb_DBTManager.h" -#define VERBOSE_ON 1 +// #define VERBOSE_ON 1 #include <dbt_debug.hpp> #include "JNIMem.hpp" @@ -43,7 +43,7 @@ void Java_direct_1bt_tinyb_DBTManager_initImpl(JNIEnv *env, jobject obj) try { DBTManager *manager = &DBTManager::get(BTMode::BT_MODE_LE); // special: static singleton setInstance<DBTManager>(env, obj, manager); - if( java_exception_check(env, E_FILE_LINE) ) { return; } + java_exception_check_and_throw(env, E_FILE_LINE); manager->setJavaObject( std::shared_ptr<JavaAnonObj>( new JavaGlobalObj(obj) ) ); JavaGlobalObj::check(manager->getJavaObject(), E_FILE_LINE); DBG_PRINT("Java_direct_1bt_tinyb_DBTManager_init: Manager %s", manager->toString().c_str()); @@ -93,9 +93,9 @@ jobject Java_direct_1bt_tinyb_DBTManager_getAdapterListImpl(JNIEnv *env, jobject // prepare adapter ctor const jstring addr = from_string_to_jstring(env, adapter->getAddressString()); const jstring name = from_string_to_jstring(env, adapter->getName()); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); jobject jAdapter = env->NewObject(clazz, clazz_ctor, (jlong)adapter, addr, name); - if( java_exception_check(env, E_FILE_LINE) ) { return nullptr; } + java_exception_check_and_throw(env, E_FILE_LINE); JNIGlobalRef::check(jAdapter, E_FILE_LINE); std::shared_ptr<JavaAnonObj> jAdapterRef = adapter->getJavaObject(); JavaGlobalObj::check(jAdapterRef, E_FILE_LINE); diff --git a/java/jni/direct_bt/DBTNativeDownlink.cxx b/java/jni/direct_bt/DBTNativeDownlink.cxx index 78270664..fdfacf5e 100644 --- a/java/jni/direct_bt/DBTNativeDownlink.cxx +++ b/java/jni/direct_bt/DBTNativeDownlink.cxx @@ -25,7 +25,7 @@ #include "direct_bt_tinyb_DBTNativeDownlink.h" -#define VERBOSE_ON 1 +// #define VERBOSE_ON 1 #include <dbt_debug.hpp> #include "JNIMem.hpp" |