Phase 5 of many to port Android backend to SimpleJNI

This commit is contained in:
Kevin Dewald 2025-05-02 00:20:50 -07:00
parent f18858b563
commit 4f818cc20b
12 changed files with 140 additions and 184 deletions

View File

@ -179,6 +179,9 @@ class ByteArray : public Object<RefType, jbyteArray> {
public:
ByteArray() = default;
// NOTE: The user is responsible for ensuring that the jobject is a jbyteArray
explicit ByteArray(jobject obj) : Object<RefType, jbyteArray>(static_cast<jbyteArray>(obj)) {}
explicit ByteArray(jbyteArray obj) : Object<RefType, jbyteArray>(obj) {}
ByteArray(const kvn::bytearray& data) : Object<RefType, jbyteArray>() {

View File

@ -82,7 +82,7 @@ int BluetoothAdapter::getState() {
BluetoothScanner BluetoothAdapter::getBluetoothLeScanner() {
if (!_obj) throw std::runtime_error("BluetoothAdapter is not initialized");
auto scanner_obj = _obj.call_object_method(_method_getBluetoothLeScanner);
return BluetoothScanner(scanner_obj.get());
return BluetoothScanner(scanner_obj);
}
std::vector<BluetoothDevice> BluetoothAdapter::getBondedDevices() {

View File

@ -1,51 +1,45 @@
#include "BluetoothScanner.h"
#include <CommonUtils.h>
#include <android/log.h>
#include <fmt/format.h>
namespace SimpleBLE {
namespace Android {
JNI::Class BluetoothScanner::_cls;
jmethodID BluetoothScanner::_method_startScan;
jmethodID BluetoothScanner::_method_stopScan;
jmethodID BluetoothScanner::_method_toString;
// Define static JNI resources
SimpleJNI::GlobalRef<jclass> BluetoothScanner::_cls;
jmethodID BluetoothScanner::_constructor = nullptr;
jmethodID BluetoothScanner::_method_startScan = nullptr;
jmethodID BluetoothScanner::_method_stopScan = nullptr;
jmethodID BluetoothScanner::_method_toString = nullptr;
void BluetoothScanner::initialize() {
JNI::Env env;
// Define the JNI descriptor
const SimpleJNI::JNIDescriptor BluetoothScanner::descriptor{
"android/bluetooth/le/BluetoothLeScanner", // Java class name
&_cls, // Where to store the jclass
{ // Methods to preload
{"<init>", "()V", &_constructor},
{"startScan", "(Landroid/bluetooth/le/ScanCallback;)V", &_method_startScan},
{"stopScan", "(Landroid/bluetooth/le/ScanCallback;)V", &_method_stopScan},
{"toString", "()Ljava/lang/String;", &_method_toString}
}};
if (_cls.get() == nullptr) {
_cls = env.find_class("android/bluetooth/le/BluetoothLeScanner");
}
const SimpleJNI::AutoRegister<BluetoothScanner> BluetoothScanner::registrar{&descriptor};
if (!_method_toString) {
_method_toString = env->GetMethodID(_cls.get(), "toString", "()Ljava/lang/String;");
}
if (!_method_startScan) {
_method_startScan = env->GetMethodID(_cls.get(), "startScan", "(Landroid/bluetooth/le/ScanCallback;)V");
}
if (!_method_stopScan) {
_method_stopScan = env->GetMethodID(_cls.get(), "stopScan", "(Landroid/bluetooth/le/ScanCallback;)V");
}
}
BluetoothScanner::BluetoothScanner(JNI::Object obj) : _obj(obj) {}
void BluetoothScanner::check_initialized() const {
if (!_obj) throw std::runtime_error("BluetoothScanner is not initialized");
}
BluetoothScanner::BluetoothScanner(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj) : _obj(obj) {}
void BluetoothScanner::startScan(Bridge::ScanCallback& callback) {
check_initialized();
if (!_obj) throw std::runtime_error("BluetoothScanner is not initialized");
_obj.call_void_method(_method_startScan, callback.get());
}
void BluetoothScanner::stopScan(Bridge::ScanCallback& callback) {
check_initialized();
if (!_obj) throw std::runtime_error("BluetoothScanner is not initialized");
_obj.call_void_method(_method_stopScan, callback.get());
}
std::string BluetoothScanner::toString() {
check_initialized();
if (!_obj) throw std::runtime_error("BluetoothScanner is not initialized");
return _obj.call_string_method(_method_toString);
}

View File

@ -1,34 +1,36 @@
#pragma once
#include "jni/Common.hpp"
#include "simplejni/Common.hpp"
#include "bridge/ScanCallback.h"
namespace SimpleBLE {
namespace Android {
class ClassHandler;
class BluetoothScanner {
public:
BluetoothScanner(JNI::Object obj);
BluetoothScanner(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj);
void startScan(Bridge::ScanCallback& callback);
void stopScan(Bridge::ScanCallback& callback);
std::string toString();
jobject get() const { return _obj.get(); } // TODO: Remove once nothing uses this
private:
static JNI::Class _cls;
// Underlying JNI object - Use SimpleJNI::Object with GlobalRef
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> _obj;
// Static JNI resources managed by Registrar
static SimpleJNI::GlobalRef<jclass> _cls;
static jmethodID _constructor;
static jmethodID _method_startScan;
static jmethodID _method_stopScan;
static jmethodID _method_toString;
static void initialize();
void check_initialized() const;
JNI::Object _obj;
friend class ClassHandler;
// JNI descriptor for auto-registration
static const SimpleJNI::JNIDescriptor descriptor;
static const SimpleJNI::AutoRegister<BluetoothScanner> registrar;
};
} // namespace Android

View File

@ -17,11 +17,7 @@ namespace SimpleBLE {
namespace Android {
void ClassHandler::initialize() {
BluetoothScanner::initialize();
ParcelUUID::initialize();
ScanResult::initialize();
ScanRecord::initialize();
SparseArrayBase::initialize();
UUID::initialize();
}

View File

@ -7,46 +7,34 @@
namespace SimpleBLE {
namespace Android {
JNI::Class ScanRecord::_cls;
// Define static JNI resources
SimpleJNI::GlobalRef<jclass> ScanRecord::_cls;
jmethodID ScanRecord::_method_getServiceUuids = nullptr;
jmethodID ScanRecord::_method_getManufacturerData = nullptr;
jmethodID ScanRecord::_method_toString = nullptr;
void ScanRecord::initialize() {
JNI::Env env;
// Define the JNI descriptor
const SimpleJNI::JNIDescriptor ScanRecord::descriptor{
"android/bluetooth/le/ScanRecord", // Java class name
&_cls, // Where to store the jclass
{ // Methods to preload
{"getServiceUuids", "()Ljava/util/List;", &_method_getServiceUuids},
{"getManufacturerSpecificData", "()Landroid/util/SparseArray;", &_method_getManufacturerData},
{"toString", "()Ljava/lang/String;", &_method_toString}
}};
if (_cls.get() == nullptr) {
_cls = env.find_class("android/bluetooth/le/ScanRecord");
}
const SimpleJNI::AutoRegister<ScanRecord> ScanRecord::registrar{&descriptor};
if (!_method_getServiceUuids) {
_method_getServiceUuids = env->GetMethodID(_cls.get(), "getServiceUuids", "()Ljava/util/List;");
}
if (!_method_getManufacturerData) {
_method_getManufacturerData = env->GetMethodID(_cls.get(), "getManufacturerSpecificData",
"()Landroid/util/SparseArray;");
}
if (!_method_toString) {
_method_toString = env->GetMethodID(_cls.get(), "toString", "()Ljava/lang/String;");
}
}
ScanRecord::ScanRecord(JNI::Object obj) : _obj(obj) {}
void ScanRecord::check_initialized() const {
if (!_obj) throw std::runtime_error("ScanRecord is not initialized");
}
ScanRecord::ScanRecord(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj) : _obj(obj) {}
std::vector<std::string> ScanRecord::getServiceUuids() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanRecord object is not initialized");
JNI::Object service_uuids_obj = _obj.call_object_method(_method_getServiceUuids);
SimpleJNI::Object<SimpleJNI::LocalRef, jobject> service_uuids_obj = _obj.call_object_method(_method_getServiceUuids);
if (!service_uuids_obj) return {};
std::vector<std::string> result;
JNI::Types::List list(service_uuids_obj);
JNI::Types::List list(service_uuids_obj.get());
JNI::Types::Iterator iterator = list.iterator();
while (iterator.hasNext()) {
ParcelUUID parcel_uuid = ParcelUUID(iterator.next());
@ -57,24 +45,24 @@ std::vector<std::string> ScanRecord::getServiceUuids() {
}
std::map<uint16_t, kvn::bytearray> ScanRecord::getManufacturerData() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanRecord object is not initialized");
JNI::Object manufacturer_data_obj = _obj.call_object_method(_method_getManufacturerData);
SimpleJNI::Object<SimpleJNI::LocalRef, jobject> manufacturer_data_obj = _obj.call_object_method(_method_getManufacturerData);
if (!manufacturer_data_obj) return {};
SparseArray<JNI::ByteArray> sparse_array(manufacturer_data_obj);
SparseArray<SimpleJNI::ByteArray<SimpleJNI::LocalRef>> sparse_array(manufacturer_data_obj);
std::map<uint16_t, kvn::bytearray> result;
for (int i = 0; i < sparse_array.size(); i++) {
uint16_t key = sparse_array.keyAt(i);
JNI::ByteArray value = sparse_array.valueAt(i);
SimpleJNI::ByteArray<SimpleJNI::LocalRef> value = sparse_array.valueAt(i);
result[key] = value.bytes();
}
return result;
}
std::string ScanRecord::toString() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanRecord object is not initialized");
return _obj.call_string_method(_method_toString);
}

View File

@ -1,20 +1,21 @@
#pragma once
#include "UUID.h"
#include "jni/Common.hpp"
#include "simplejni/Common.hpp"
#include "simplejni/Registry.hpp"
#include "UUID.h"
#include <map>
#include <vector>
#include "kvn/kvn_bytearray.h"
#include "ParcelUUID.h"
#include "SparseArray.h"
namespace SimpleBLE {
namespace Android {
class ClassHandler;
class ScanRecord {
public:
ScanRecord(JNI::Object obj);
ScanRecord(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj);
std::vector<std::string> getServiceUuids();
std::map<uint16_t, kvn::bytearray> getManufacturerData();
@ -22,16 +23,18 @@ class ScanRecord {
std::string toString();
private:
static JNI::Class _cls;
// Underlying JNI object
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> _obj;
// Static JNI resources managed by Registrar
static SimpleJNI::GlobalRef<jclass> _cls;
static jmethodID _method_getServiceUuids;
static jmethodID _method_getManufacturerData;
static jmethodID _method_toString;
static void initialize();
void check_initialized() const;
JNI::Object _obj;
friend class ClassHandler;
// JNI descriptor for auto-registration
static const SimpleJNI::JNIDescriptor descriptor;
static const SimpleJNI::AutoRegister<ScanRecord> registrar;
};
} // namespace Android

View File

@ -1,10 +1,10 @@
#include "ScanResult.h"
namespace SimpleBLE {
namespace Android {
JNI::Class ScanResult::_cls;
// Define static JNI resources
SimpleJNI::GlobalRef<jclass> ScanResult::_cls;
jmethodID ScanResult::_method_getDevice = nullptr;
jmethodID ScanResult::_method_getRssi = nullptr;
jmethodID ScanResult::_method_getTxPower = nullptr;
@ -12,71 +12,50 @@ jmethodID ScanResult::_method_isConnectable = nullptr;
jmethodID ScanResult::_method_getScanRecord = nullptr;
jmethodID ScanResult::_method_toString = nullptr;
void ScanResult::initialize() {
JNI::Env env;
// Define the JNI descriptor
const SimpleJNI::JNIDescriptor ScanResult::descriptor{
"android/bluetooth/le/ScanResult", // Java class name
&_cls, // Where to store the jclass
{ // Methods to preload
{"getDevice", "()Landroid/bluetooth/BluetoothDevice;", &_method_getDevice},
{"getRssi", "()I", &_method_getRssi},
{"getTxPower", "()I", &_method_getTxPower},
{"isConnectable", "()Z", &_method_isConnectable},
{"getScanRecord", "()Landroid/bluetooth/le/ScanRecord;", &_method_getScanRecord},
{"toString", "()Ljava/lang/String;", &_method_toString}
}};
if (_cls.get() == nullptr) {
_cls = env.find_class("android/bluetooth/le/ScanResult");
}
const SimpleJNI::AutoRegister<ScanResult> ScanResult::registrar{&descriptor};
if (!_method_getDevice) {
_method_getDevice = env->GetMethodID(_cls.get(), "getDevice", "()Landroid/bluetooth/BluetoothDevice;");
}
if (!_method_getRssi) {
_method_getRssi = env->GetMethodID(_cls.get(), "getRssi", "()I");
}
if (!_method_getTxPower) {
_method_getTxPower = env->GetMethodID(_cls.get(), "getTxPower", "()I");
}
if (!_method_isConnectable) {
_method_isConnectable = env->GetMethodID(_cls.get(), "isConnectable", "()Z");
}
if (!_method_getScanRecord) {
_method_getScanRecord = env->GetMethodID(_cls.get(), "getScanRecord", "()Landroid/bluetooth/le/ScanRecord;");
}
if (!_method_toString) {
_method_toString = env->GetMethodID(_cls.get(), "toString", "()Ljava/lang/String;");
}
}
ScanResult::ScanResult(JNI::Object obj) : _obj(obj) {}
void ScanResult::check_initialized() const {
if (!_obj) throw std::runtime_error("ScanResult is not initialized");
}
ScanResult::ScanResult(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj) : _obj(obj) {}
BluetoothDevice ScanResult::getDevice() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return BluetoothDevice(_obj.call_object_method(_method_getDevice));
}
int16_t ScanResult::getRssi() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return _obj.call_int_method(_method_getRssi);
}
int16_t ScanResult::getTxPower() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return _obj.call_int_method(_method_getTxPower);
}
bool ScanResult::isConnectable() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return _obj.call_boolean_method(_method_isConnectable);
}
ScanRecord ScanResult::getScanRecord() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return ScanRecord(_obj.call_object_method(_method_getScanRecord));
}
std::string ScanResult::toString() {
check_initialized();
if (!_obj) throw std::runtime_error("ScanResult object is not initialized");
return _obj.call_string_method(_method_toString);
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "jni/Common.hpp"
#include "simplejni/Common.hpp"
#include "simplejni/Registry.hpp"
#include "BluetoothDevice.h"
#include "ScanRecord.h"
@ -8,11 +9,9 @@
namespace SimpleBLE {
namespace Android {
class ClassHandler;
class ScanResult {
public:
ScanResult(JNI::Object obj);
ScanResult(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj);
BluetoothDevice getDevice();
int16_t getRssi();
@ -22,7 +21,11 @@ class ScanResult {
std::string toString();
private:
static JNI::Class _cls;
// Underlying JNI object
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> _obj;
// Static JNI resources managed by Registrar
static SimpleJNI::GlobalRef<jclass> _cls;
static jmethodID _method_getDevice;
static jmethodID _method_getRssi;
static jmethodID _method_getTxPower;
@ -30,11 +33,9 @@ class ScanResult {
static jmethodID _method_getScanRecord;
static jmethodID _method_toString;
static void initialize();
void check_initialized() const;
JNI::Object _obj;
friend class ClassHandler;
// JNI descriptor for auto-registration
static const SimpleJNI::JNIDescriptor descriptor;
static const SimpleJNI::AutoRegister<ScanResult> registrar;
};
} // namespace Android

View File

@ -3,62 +3,52 @@
namespace SimpleBLE {
namespace Android {
JNI::Class SparseArrayBase::_cls;
// Define static JNI resources
SimpleJNI::GlobalRef<jclass> SparseArrayBase::_cls;
jmethodID SparseArrayBase::_method_size = nullptr;
jmethodID SparseArrayBase::_method_keyAt = nullptr;
jmethodID SparseArrayBase::_method_valueAt = nullptr;
void SparseArrayBase::initialize() {
JNI::Env env;
// Define the JNI descriptor
const SimpleJNI::JNIDescriptor SparseArrayBase::descriptor{
"android/util/SparseArray", // Java class name
&_cls, // Where to store the jclass
{ // Methods to preload
{"size", "()I", &_method_size},
{"keyAt", "(I)I", &_method_keyAt},
{"valueAt", "(I)Ljava/lang/Object;", &_method_valueAt}
}};
if (_cls.get() == nullptr) {
_cls = env.find_class("android/util/SparseArray");
}
if (!_method_size) {
_method_size = env->GetMethodID(_cls.get(), "size", "()I");
}
if (!_method_keyAt) {
_method_keyAt = env->GetMethodID(_cls.get(), "keyAt", "(I)I");
}
if (!_method_valueAt) {
_method_valueAt = env->GetMethodID(_cls.get(), "valueAt", "(I)Ljava/lang/Object;");
}
}
const SimpleJNI::AutoRegister<SparseArrayBase> SparseArrayBase::registrar{&descriptor};
template <typename T>
SparseArray<T>::SparseArray() {}
template <typename T>
SparseArray<T>::SparseArray(JNI::Object obj) : _obj(obj) {}
SparseArray<T>::SparseArray(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj) : _obj(obj) {}
template <typename T>
void SparseArray<T>::check_initialized() const {
if (!_obj) throw std::runtime_error("SparseArray is not initialized");
}
template <typename T>
int SparseArray<T>::size() {
check_initialized();
if (!_obj) throw std::runtime_error("SparseArray is not initialized");
return _obj.call_int_method(_method_size);
}
template <typename T>
int SparseArray<T>::keyAt(int index) {
check_initialized();
if (!_obj) throw std::runtime_error("SparseArray is not initialized");
return _obj.call_int_method(_method_keyAt, index);
}
template <typename T>
T SparseArray<T>::valueAt(int index) {
check_initialized();
return T(_obj.call_object_method(_method_valueAt, index));
if (!_obj) throw std::runtime_error("SparseArray is not initialized");
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> value = _obj.call_object_method(_method_valueAt, index);
return T(value.get());
}
template class SparseArray<JNI::ByteArray>;
template class SparseArray<JNI::Object>;
template class SparseArray<SimpleJNI::ByteArray<SimpleJNI::LocalRef>>;
template class SparseArray<SimpleJNI::Object<SimpleJNI::LocalRef, jobject>>;
} // namespace Android
} // namespace SimpleBLE

View File

@ -1,37 +1,36 @@
#pragma once
#include "jni/Common.hpp"
#include "simplejni/Common.hpp"
#include "simplejni/Registry.hpp"
namespace SimpleBLE {
namespace Android {
class ClassHandler;
class SparseArrayBase {
public:
static void initialize();
static JNI::Class _cls;
// Static JNI resources managed by Registrar
static SimpleJNI::GlobalRef<jclass> _cls;
static jmethodID _method_size;
static jmethodID _method_keyAt;
static jmethodID _method_valueAt;
friend class ClassHandler;
// JNI descriptor for auto-registration
static const SimpleJNI::JNIDescriptor descriptor;
static const SimpleJNI::AutoRegister<SparseArrayBase> registrar;
};
template <typename T>
class SparseArray : public SparseArrayBase {
public:
SparseArray();
SparseArray(JNI::Object obj);
SparseArray(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> obj);
int size();
int keyAt(int index);
T valueAt(int index);
private:
void check_initialized() const;
JNI::Object _obj;
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> _obj;
};
} // namespace Android

View File

@ -117,7 +117,8 @@ extern "C" {
// clang-format off
JNIEXPORT void JNICALL Java_org_simpleble_android_bridge_ScanCallback_onScanResultCallback(JNIEnv *env, jobject thiz, jint callback_type, jobject result) {
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> thiz_obj(thiz);
SimpleBLE::Android::ScanResult scan_result(result);
SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> result_obj(result);
SimpleBLE::Android::ScanResult scan_result(result_obj);
SimpleJNI::Runner::get().enqueue([thiz_obj, callback_type, scan_result]() {
SimpleBLE::Android::Bridge::ScanCallback::jni_onScanResultCallback(thiz_obj, callback_type, scan_result);
});