| /* |
| * Copyright (c) 2020-2021 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| /** |
| * @file |
| * Implementation of JNI bridge for CHIP Device Controller for Android apps |
| * |
| */ |
| |
| #include "AndroidBleApplicationDelegate.h" |
| #include "AndroidBleConnectionDelegate.h" |
| #include "AndroidBlePlatformDelegate.h" |
| #include "AndroidCallbacks.h" |
| #include "AndroidDeviceControllerWrapper.h" |
| #include "CHIPJNIError.h" |
| #include "JniReferences.h" |
| #include "JniTypeWrappers.h" |
| #include "MdnsImpl.h" |
| #include "StackLock.h" |
| |
| #include <app/chip-zcl-zpro-codec.h> |
| #include <atomic> |
| #include <ble/BleUUID.h> |
| #include <controller/CHIPDeviceController.h> |
| #include <jni.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/ErrorStr.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/ThreadOperationalDataset.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/KeyValueStoreManager.h> |
| #include <protocols/Protocols.h> |
| #include <protocols/temp_zcl/TempZCL.h> |
| #include <pthread.h> |
| #include <system/SystemLayerImpl.h> |
| |
| // Choose an approximation of PTHREAD_NULL if pthread.h doesn't define one. |
| #ifndef PTHREAD_NULL |
| #define PTHREAD_NULL 0 |
| #endif // PTHREAD_NULL |
| |
| using namespace chip; |
| using namespace chip::Inet; |
| using namespace chip::Controller; |
| |
| #define JNI_METHOD(RETURN, METHOD_NAME) \ |
| extern "C" JNIEXPORT RETURN JNICALL Java_chip_devicecontroller_ChipDeviceController_##METHOD_NAME |
| |
| #define JNI_ANDROID_CHIP_STACK_METHOD(RETURN, METHOD_NAME) \ |
| extern "C" JNIEXPORT RETURN JNICALL Java_chip_devicecontroller_AndroidChipStack_##METHOD_NAME |
| |
| #define CDC_JNI_CALLBACK_LOCAL_REF_COUNT 256 |
| |
| static void GetCHIPDevice(JNIEnv * env, long wrapperHandle, uint64_t deviceId, Device ** device); |
| static void HandleNotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj); |
| static bool HandleSendCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId, |
| const uint8_t * characteristicData, uint32_t characteristicDataLen); |
| static bool HandleSubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId); |
| static bool HandleUnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId); |
| static bool HandleCloseConnection(BLE_CONNECTION_OBJECT connObj); |
| static uint16_t HandleGetMTU(BLE_CONNECTION_OBJECT connObj); |
| static void HandleNewConnection(void * appState, const uint16_t discriminator); |
| static void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow); |
| static void ReportError(JNIEnv * env, CHIP_ERROR cbErr, const char * cbName); |
| static void * IOThreadMain(void * arg); |
| static CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx); |
| |
| namespace { |
| |
| JavaVM * sJVM; |
| System::LayerImpl sSystemLayer; |
| Inet::InetLayer sInetLayer; |
| |
| #if CONFIG_NETWORK_LAYER_BLE |
| Ble::BleLayer sBleLayer; |
| AndroidBleApplicationDelegate sBleApplicationDelegate; |
| AndroidBlePlatformDelegate sBlePlatformDelegate; |
| AndroidBleConnectionDelegate sBleConnectionDelegate; |
| #endif |
| |
| pthread_t sIOThread = PTHREAD_NULL; |
| bool sShutdown = false; |
| |
| jclass sAndroidChipStackCls = NULL; |
| jclass sChipDeviceControllerExceptionCls = NULL; |
| |
| } // namespace |
| |
| // NOTE: Remote device ID is in sync with the echo server device id |
| // At some point, we may want to add an option to connect to a device without |
| // knowing its id, because the ID can be learned on the first response that is received. |
| chip::NodeId kLocalDeviceId = chip::kTestControllerNodeId; |
| chip::NodeId kRemoteDeviceId = chip::kTestDeviceNodeId; |
| |
| #if CONFIG_NETWORK_LAYER_BLE |
| |
| chip::Ble::BleLayer * GetJNIBleLayer() |
| { |
| return &sBleLayer; |
| } |
| |
| #endif |
| |
| jint JNI_OnLoad(JavaVM * jvm, void * reserved) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env; |
| int pthreadErr = 0; |
| |
| ChipLogProgress(Controller, "JNI_OnLoad() called"); |
| |
| chip::Platform::MemoryInit(); |
| |
| // Save a reference to the JVM. Will need this to call back into Java. |
| JniReferences::GetInstance().SetJavaVm(jvm); |
| sJVM = jvm; |
| |
| // Get a JNI environment object. |
| env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| ChipLogProgress(Controller, "Loading Java class references."); |
| |
| // Get various class references need by the API. |
| err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/AndroidChipStack", sAndroidChipStackCls); |
| SuccessOrExit(err); |
| err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/ChipDeviceControllerException", |
| sChipDeviceControllerExceptionCls); |
| SuccessOrExit(err); |
| ChipLogProgress(Controller, "Java class references loaded."); |
| |
| // Initialize the CHIP System Layer. |
| err = sSystemLayer.Init(); |
| SuccessOrExit(err); |
| |
| // Initialize the CHIP Inet layer. |
| err = sInetLayer.Init(sSystemLayer, NULL); |
| SuccessOrExit(err); |
| ChipLogProgress(Controller, "Inet layer initialized."); |
| |
| #if CONFIG_NETWORK_LAYER_BLE |
| ChipLogProgress(Controller, "BLE Layer being configured."); |
| |
| // Initialize the BleApplicationDelegate |
| sBleApplicationDelegate.SetNotifyChipConnectionClosedCallback(HandleNotifyChipConnectionClosed); |
| // Initialize the BlePlatformDelegate |
| sBlePlatformDelegate.SetSendWriteRequestCallback(HandleSendCharacteristic); |
| sBlePlatformDelegate.SetSubscribeCharacteristicCallback(HandleSubscribeCharacteristic); |
| sBlePlatformDelegate.SetUnsubscribeCharacteristicCallback(HandleUnsubscribeCharacteristic); |
| sBlePlatformDelegate.SetCloseConnectionCallback(HandleCloseConnection); |
| sBlePlatformDelegate.SetGetMTUCallback(HandleGetMTU); |
| // Initialize the BleConnectionDelegate |
| sBleConnectionDelegate.SetNewConnectionCallback(HandleNewConnection); |
| |
| ChipLogProgress(Controller, "Asking for BLE Layer initialization."); |
| // Initialize the BleLayer object. |
| err = sBleLayer.Init(&sBlePlatformDelegate, &sBleConnectionDelegate, &sBleApplicationDelegate, &sSystemLayer); |
| SuccessOrExit(err); |
| |
| ChipLogProgress(Controller, "BLE was initialized."); |
| #endif |
| |
| // Create and start the IO thread. |
| sShutdown = false; |
| pthreadErr = pthread_create(&sIOThread, NULL, IOThreadMain, NULL); |
| VerifyOrExit(pthreadErr == 0, err = CHIP_ERROR_POSIX(pthreadErr)); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ThrowError(env, err); |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| JNI_OnUnload(jvm, reserved); |
| } |
| |
| return (err == CHIP_NO_ERROR) ? JNI_VERSION_1_6 : JNI_ERR; |
| } |
| |
| void JNI_OnUnload(JavaVM * jvm, void * reserved) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| ChipLogProgress(Controller, "JNI_OnUnload() called"); |
| |
| // If the IO thread has been started, shut it down and wait for it to exit. |
| if (sIOThread != PTHREAD_NULL) |
| { |
| sShutdown = true; |
| sSystemLayer.Signal(); |
| |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| pthread_join(sIOThread, NULL); |
| } |
| |
| #if CONFIG_NETWORK_LAYER_BLE |
| sBleLayer.Shutdown(); |
| #endif |
| |
| sInetLayer.Shutdown(); |
| sSystemLayer.Shutdown(); |
| sJVM = NULL; |
| |
| chip::Platform::MemoryShutdown(); |
| } |
| |
| JNI_METHOD(jlong, newDeviceController) |
| (JNIEnv * env, jobject self, jobject keyValueStoreManager, jobject serviceResolver, jobject chipMdnsCallback) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = NULL; |
| long result = 0; |
| |
| ChipLogProgress(Controller, "newDeviceController() called"); |
| |
| DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().InitializeWithObject(keyValueStoreManager); |
| using ::chip::Mdns::InitializeWithObjects; |
| InitializeWithObjects(serviceResolver, chipMdnsCallback); |
| |
| wrapper = AndroidDeviceControllerWrapper::AllocateNew(sJVM, self, JniReferences::GetInstance().GetStackLock(), kLocalDeviceId, |
| &sSystemLayer, &sInetLayer, &err); |
| SuccessOrExit(err); |
| |
| result = wrapper->ToJNIHandle(); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| if (wrapper != NULL) |
| { |
| delete wrapper; |
| } |
| |
| if (err != CHIP_JNI_ERROR_EXCEPTION_THROWN) |
| { |
| ThrowError(env, err); |
| } |
| } |
| |
| return result; |
| } |
| |
| JNI_METHOD(void, pairDevice) |
| (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jint connObj, jlong pinCode, jbyteArray csrNonce) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| ChipLogProgress(Controller, "pairDevice() called with device ID, connection object, and pincode"); |
| |
| sBleLayer.mAppState = (void *) self; |
| RendezvousParameters params = RendezvousParameters() |
| .SetSetupPINCode(pinCode) |
| .SetConnectionObject(reinterpret_cast<BLE_CONNECTION_OBJECT>(connObj)) |
| .SetBleLayer(&sBleLayer) |
| .SetPeerAddress(Transport::PeerAddress::BLE()); |
| if (csrNonce != nullptr) |
| { |
| JniByteArray jniCsrNonce(env, csrNonce); |
| params.SetCSRNonce(jniCsrNonce.byteSpan()); |
| } |
| err = wrapper->Controller()->PairDevice(deviceId, params); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to pair the device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(void, pairDeviceWithAddress) |
| (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jstring address, jint port, jint discriminator, jint pinCode, |
| jbyteArray csrNonce) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| ChipLogProgress(Controller, "pairDeviceWithAddress() called"); |
| |
| Inet::IPAddress addr; |
| JniUtfString addrJniString(env, address); |
| VerifyOrReturn(Inet::IPAddress::FromString(addrJniString.c_str(), addr), |
| ChipLogError(Controller, "Failed to parse IP address."), ThrowError(env, CHIP_ERROR_INVALID_ARGUMENT)); |
| |
| RendezvousParameters params = RendezvousParameters() |
| .SetDiscriminator(discriminator) |
| .SetSetupPINCode(pinCode) |
| .SetPeerAddress(Transport::PeerAddress::UDP(addr, port)); |
| if (csrNonce != nullptr) |
| { |
| JniByteArray jniCsrNonce(env, csrNonce); |
| params.SetCSRNonce(jniCsrNonce.byteSpan()); |
| } |
| err = wrapper->Controller()->PairDevice(deviceId, params); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to pair the device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(void, unpairDevice)(JNIEnv * env, jobject self, jlong handle, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| ChipLogProgress(Controller, "unpairDevice() called with device ID"); |
| |
| err = wrapper->Controller()->UnpairDevice(deviceId); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to unpair the device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(void, stopDevicePairing)(JNIEnv * env, jobject self, jlong handle, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| ChipLogProgress(Controller, "stopDevicePairing() called with device ID"); |
| |
| err = wrapper->Controller()->StopPairing(deviceId); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to unpair the device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(jlong, getDevicePointer)(JNIEnv * env, jobject self, jlong handle, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| Device * chipDevice = nullptr; |
| |
| ChipLogProgress(Controller, "getDevicePointer() called with device ID"); |
| |
| GetCHIPDevice(env, handle, deviceId, &chipDevice); |
| |
| static_assert(sizeof(jlong) >= sizeof(void *), "Need to store a pointer in a Java handle"); |
| return reinterpret_cast<jlong>(chipDevice); |
| } |
| |
| JNI_METHOD(void, getConnectedDevicePointer)(JNIEnv * env, jobject self, jlong handle, jlong nodeId, jlong callbackHandle) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| GetConnectedDeviceCallback * connectedDeviceCallback = reinterpret_cast<GetConnectedDeviceCallback *>(callbackHandle); |
| VerifyOrReturn(connectedDeviceCallback != nullptr, ChipLogError(Controller, "GetConnectedDeviceCallback handle is nullptr")); |
| wrapper->Controller()->GetConnectedDevice(nodeId, &connectedDeviceCallback->mOnSuccess, &connectedDeviceCallback->mOnFailure); |
| } |
| |
| JNI_METHOD(void, pairTestDeviceWithoutSecurity)(JNIEnv * env, jobject self, jlong handle, jstring deviceAddr) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| chip::Inet::IPAddress deviceIPAddr; |
| |
| ChipLogProgress(Controller, "pairTestDeviceWithoutSecurity() called with IP Address"); |
| |
| const char * deviceAddrStr = env->GetStringUTFChars(deviceAddr, 0); |
| deviceIPAddr.FromString(deviceAddrStr, deviceIPAddr); |
| env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr); |
| |
| Controller::SerializedDevice mSerializedTestDevice; |
| err = wrapper->Controller()->PairTestDeviceWithoutSecurity(kRemoteDeviceId, chip::Transport::PeerAddress::UDP(deviceIPAddr), |
| mSerializedTestDevice); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to connect to device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(void, disconnectDevice)(JNIEnv * env, jobject self, jlong handle, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| Device * chipDevice = nullptr; |
| |
| ChipLogProgress(Controller, "disconnectDevice() called with deviceId"); |
| |
| err = wrapper->Controller()->GetDevice(deviceId, &chipDevice); |
| |
| if (err != CHIP_NO_ERROR || !chipDevice) |
| { |
| ChipLogError(Controller, "Failed to get paired device."); |
| ThrowError(env, err); |
| } |
| |
| wrapper->Controller()->ReleaseDevice(chipDevice); |
| } |
| |
| JNI_METHOD(jboolean, isActive)(JNIEnv * env, jobject self, jlong handle) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| |
| Device * chipDevice = reinterpret_cast<Device *>(handle); |
| return chipDevice->IsActive(); |
| } |
| |
| void GetCHIPDevice(JNIEnv * env, long wrapperHandle, uint64_t deviceId, Device ** chipDevice) |
| { |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(wrapperHandle); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| err = wrapper->Controller()->GetDevice(deviceId, chipDevice); |
| |
| if (err != CHIP_NO_ERROR || !chipDevice) |
| { |
| ChipLogError(Controller, "Failed to get paired device."); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(jstring, getIpAddress)(JNIEnv * env, jobject self, jlong handle, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| Device * chipDevice = nullptr; |
| |
| GetCHIPDevice(env, handle, deviceId, &chipDevice); |
| |
| chip::Inet::IPAddress addr; |
| uint16_t port; |
| char addrStr[50]; |
| |
| if (!chipDevice->GetAddress(addr, port)) |
| return nullptr; |
| |
| addr.ToString(addrStr); |
| return env->NewStringUTF(addrStr); |
| } |
| |
| JNI_METHOD(void, updateDevice)(JNIEnv * env, jobject self, jlong handle, jlong fabricId, jlong deviceId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| CHIP_ERROR err = wrapper->Controller()->UpdateDevice(static_cast<chip::NodeId>(deviceId)); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "Failed to update device"); |
| ThrowError(env, err); |
| } |
| } |
| |
| JNI_METHOD(jboolean, openPairingWindow)(JNIEnv * env, jobject self, jlong handle, jlong deviceId, jint duration) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| Device * chipDevice = nullptr; |
| chip::SetupPayload setupPayload; |
| |
| GetCHIPDevice(env, handle, deviceId, &chipDevice); |
| |
| err = chipDevice->OpenPairingWindow(duration, chip::Controller::Device::CommissioningWindowOption::kOriginalSetupCode, |
| setupPayload); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "OpenPairingWindow failed: %" CHIP_ERROR_FORMAT, err.Format()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool JavaBytesToUUID(JNIEnv * env, jbyteArray value, chip::Ble::ChipBleUUID & uuid) |
| { |
| const auto valueBegin = env->GetByteArrayElements(value, nullptr); |
| const auto valueLength = env->GetArrayLength(value); |
| bool result = true; |
| |
| VerifyOrExit(valueBegin && valueLength == sizeof(uuid.bytes), result = false); |
| memcpy(uuid.bytes, valueBegin, valueLength); |
| |
| exit: |
| env->ReleaseByteArrayElements(value, valueBegin, 0); |
| return result; |
| } |
| |
| JNI_ANDROID_CHIP_STACK_METHOD(void, handleIndicationReceived) |
| (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId, jbyteArray value) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn); |
| const auto valueBegin = env->GetByteArrayElements(value, nullptr); |
| const auto valueLength = env->GetArrayLength(value); |
| |
| chip::Ble::ChipBleUUID svcUUID; |
| chip::Ble::ChipBleUUID charUUID; |
| chip::System::PacketBufferHandle buffer; |
| |
| VerifyOrExit(JavaBytesToUUID(env, svcId, svcUUID), |
| ChipLogError(Controller, "handleIndicationReceived() called with invalid service ID")); |
| VerifyOrExit(JavaBytesToUUID(env, charId, charUUID), |
| ChipLogError(Controller, "handleIndicationReceived() called with invalid characteristic ID")); |
| |
| buffer = System::PacketBufferHandle::NewWithData(valueBegin, valueLength); |
| VerifyOrExit(!buffer.IsNull(), ChipLogError(Controller, "Failed to allocate packet buffer")); |
| |
| sBleLayer.HandleIndicationReceived(connObj, &svcUUID, &charUUID, std::move(buffer)); |
| exit: |
| env->ReleaseByteArrayElements(value, valueBegin, 0); |
| } |
| |
| JNI_ANDROID_CHIP_STACK_METHOD(void, handleWriteConfirmation) |
| (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn); |
| |
| chip::Ble::ChipBleUUID svcUUID; |
| chip::Ble::ChipBleUUID charUUID; |
| VerifyOrReturn(JavaBytesToUUID(env, svcId, svcUUID), |
| ChipLogError(Controller, "handleWriteConfirmation() called with invalid service ID")); |
| VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID), |
| ChipLogError(Controller, "handleWriteConfirmation() called with invalid characteristic ID")); |
| |
| sBleLayer.HandleWriteConfirmation(connObj, &svcUUID, &charUUID); |
| } |
| |
| JNI_ANDROID_CHIP_STACK_METHOD(void, handleSubscribeComplete) |
| (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn); |
| |
| chip::Ble::ChipBleUUID svcUUID; |
| chip::Ble::ChipBleUUID charUUID; |
| VerifyOrReturn(JavaBytesToUUID(env, svcId, svcUUID), |
| ChipLogError(Controller, "handleSubscribeComplete() called with invalid service ID")); |
| VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID), |
| ChipLogError(Controller, "handleSubscribeComplete() called with invalid characteristic ID")); |
| |
| sBleLayer.HandleSubscribeComplete(connObj, &svcUUID, &charUUID); |
| } |
| |
| JNI_ANDROID_CHIP_STACK_METHOD(void, handleUnsubscribeComplete) |
| (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn); |
| |
| chip::Ble::ChipBleUUID svcUUID; |
| chip::Ble::ChipBleUUID charUUID; |
| VerifyOrReturn(JavaBytesToUUID(env, svcId, svcUUID), |
| ChipLogError(Controller, "handleUnsubscribeComplete() called with invalid service ID")); |
| VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID), |
| ChipLogError(Controller, "handleUnsubscribeComplete() called with invalid characteristic ID")); |
| |
| sBleLayer.HandleUnsubscribeComplete(connObj, &svcUUID, &charUUID); |
| } |
| |
| JNI_ANDROID_CHIP_STACK_METHOD(void, handleConnectionError)(JNIEnv * env, jobject self, jint conn) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn); |
| |
| sBleLayer.HandleConnectionError(connObj, BLE_ERROR_APP_CLOSED_CONNECTION); |
| } |
| |
| JNI_METHOD(void, deleteDeviceController)(JNIEnv * env, jobject self, jlong handle) |
| { |
| StackLockGuard lock(JniReferences::GetInstance().GetStackLock()); |
| AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); |
| |
| ChipLogProgress(Controller, "deleteDeviceController() called"); |
| |
| if (wrapper != NULL) |
| { |
| delete wrapper; |
| } |
| } |
| |
| void HandleNotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jmethodID method; |
| intptr_t tmpConnObj; |
| |
| ChipLogProgress(Controller, "Received NotifyChipConnectionClosed"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onNotifyChipConnectionClosed", "(I)V"); |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| ChipLogProgress(Controller, "Calling Java NotifyChipConnectionClosed"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| env->CallStaticVoidMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj)); |
| } |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| } |
| env->ExceptionClear(); |
| } |
| |
| bool HandleSendCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId, |
| const uint8_t * characteristicData, uint32_t characteristicDataLen) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jbyteArray svcIdObj; |
| jbyteArray charIdObj; |
| jbyteArray characteristicDataObj; |
| jmethodID method; |
| intptr_t tmpConnObj; |
| bool rc = false; |
| |
| ChipLogProgress(Controller, "Received SendCharacteristic"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); |
| SuccessOrExit(err); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); |
| SuccessOrExit(err); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, characteristicData, characteristicDataLen, characteristicDataObj); |
| SuccessOrExit(err); |
| |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onSendCharacteristic", "(I[B[B[B)Z"); |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java SendCharacteristic"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| rc = (bool) env->CallStaticBooleanMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj), svcIdObj, charIdObj, |
| characteristicDataObj); |
| } |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| rc = false; |
| } |
| env->ExceptionClear(); |
| |
| return rc; |
| } |
| |
| bool HandleSubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jbyteArray svcIdObj; |
| jbyteArray charIdObj; |
| jmethodID method; |
| intptr_t tmpConnObj; |
| bool rc = false; |
| |
| ChipLogProgress(Controller, "Received SubscribeCharacteristic"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); |
| SuccessOrExit(err); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); |
| SuccessOrExit(err); |
| |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onSubscribeCharacteristic", "(I[B[B)Z"); |
| } |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java SubscribeCharacteristic"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| rc = (bool) env->CallStaticBooleanMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj), svcIdObj, charIdObj); |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| rc = false; |
| } |
| env->ExceptionClear(); |
| |
| return rc; |
| } |
| |
| bool HandleUnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svcId, const uint8_t * charId) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jbyteArray svcIdObj; |
| jbyteArray charIdObj; |
| jmethodID method; |
| intptr_t tmpConnObj; |
| bool rc = false; |
| |
| ChipLogProgress(Controller, "Received UnsubscribeCharacteristic"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); |
| SuccessOrExit(err); |
| |
| err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); |
| SuccessOrExit(err); |
| |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onUnsubscribeCharacteristic", "(I[B[B)Z"); |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java UnsubscribeCharacteristic"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| rc = (bool) env->CallStaticBooleanMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj), svcIdObj, charIdObj); |
| } |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| rc = false; |
| } |
| env->ExceptionClear(); |
| |
| return rc; |
| } |
| |
| bool HandleCloseConnection(BLE_CONNECTION_OBJECT connObj) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jmethodID method; |
| intptr_t tmpConnObj; |
| bool rc = false; |
| |
| ChipLogProgress(Controller, "Received CloseConnection"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onCloseConnection", "(I)Z"); |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java CloseConnection"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| rc = (bool) env->CallStaticBooleanMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj)); |
| } |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| rc = false; |
| } |
| env->ExceptionClear(); |
| return rc; |
| } |
| |
| uint16_t HandleGetMTU(BLE_CONNECTION_OBJECT connObj) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jmethodID method; |
| intptr_t tmpConnObj; |
| uint16_t mtu = 0; |
| |
| ChipLogProgress(Controller, "Received GetMTU"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| method = env->GetStaticMethodID(sAndroidChipStackCls, "onGetMTU", "(I)I"); |
| } |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java onGetMTU"); |
| |
| env->ExceptionClear(); |
| tmpConnObj = reinterpret_cast<intptr_t>(connObj); |
| mtu = (int16_t) env->CallStaticIntMethod(sAndroidChipStackCls, method, static_cast<jint>(tmpConnObj)); |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| mtu = 0; |
| } |
| env->ExceptionClear(); |
| |
| return mtu; |
| } |
| |
| void HandleNewConnection(void * appState, const uint16_t discriminator) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); |
| jmethodID method; |
| jclass deviceControllerCls; |
| AndroidDeviceControllerWrapper * wrapper = reinterpret_cast<AndroidDeviceControllerWrapper *>(appState); |
| jobject self = wrapper->JavaObjectRef(); |
| |
| ChipLogProgress(Controller, "Received New Connection"); |
| VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); |
| |
| deviceControllerCls = env->GetObjectClass(self); |
| VerifyOrExit(deviceControllerCls != NULL, err = CHIP_JNI_ERROR_TYPE_NOT_FOUND); |
| |
| method = env->GetMethodID(deviceControllerCls, "onConnectDeviceComplete", "()V"); |
| VerifyOrExit(method != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| ChipLogProgress(Controller, "Calling Java onConnectDeviceComplete"); |
| |
| env->ExceptionClear(); |
| { |
| StackUnlockGuard unlockGuard(JniReferences::GetInstance().GetStackLock()); |
| env->CallVoidMethod(self, method); |
| } |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ReportError(env, err, __FUNCTION__); |
| } |
| env->ExceptionClear(); |
| } |
| |
| void * IOThreadMain(void * arg) |
| { |
| JNIEnv * env; |
| JavaVMAttachArgs attachArgs; |
| |
| // Attach the IO thread to the JVM as a daemon thread. |
| // This allows the JVM to shutdown without waiting for this thread to exit. |
| attachArgs.version = JNI_VERSION_1_6; |
| attachArgs.name = (char *) "CHIP Device Controller IO Thread"; |
| attachArgs.group = NULL; |
| #ifdef __ANDROID__ |
| sJVM->AttachCurrentThreadAsDaemon(&env, (void *) &attachArgs); |
| #else |
| sJVM->AttachCurrentThreadAsDaemon((void **) &env, (void *) &attachArgs); |
| #endif |
| |
| // Set to true to quit the loop. This is currently unused. |
| std::atomic<bool> quit; |
| |
| ChipLogProgress(Controller, "IO thread starting"); |
| |
| // Lock the stack to prevent collisions with Java threads. |
| pthread_mutex_lock(JniReferences::GetInstance().GetStackLock()); |
| |
| sSystemLayer.EventLoopBegins(); |
| |
| // Loop until we are told to exit. |
| while (!quit.load(std::memory_order_relaxed)) |
| { |
| sSystemLayer.PrepareEvents(); |
| |
| // Unlock the stack so that Java threads can make API calls. |
| pthread_mutex_unlock(JniReferences::GetInstance().GetStackLock()); |
| |
| sSystemLayer.WaitForEvents(); |
| |
| // Break the loop if requested to shutdown. |
| // if (sShutdown) |
| // break; |
| |
| // Re-lock the stack. |
| pthread_mutex_lock(JniReferences::GetInstance().GetStackLock()); |
| |
| sSystemLayer.HandleEvents(); |
| } |
| sSystemLayer.EventLoopEnds(); |
| |
| // Detach the thread from the JVM. |
| sJVM->DetachCurrentThread(); |
| |
| return NULL; |
| } |
| |
| void ReportError(JNIEnv * env, CHIP_ERROR cbErr, const char * functName) |
| { |
| if (cbErr == CHIP_JNI_ERROR_EXCEPTION_THROWN) |
| { |
| ChipLogError(Controller, "Java exception thrown in %s", functName); |
| env->ExceptionDescribe(); |
| } |
| else |
| { |
| const char * errStr; |
| switch (cbErr.AsInteger()) |
| { |
| case CHIP_JNI_ERROR_TYPE_NOT_FOUND.AsInteger(): |
| errStr = "JNI type not found"; |
| break; |
| case CHIP_JNI_ERROR_METHOD_NOT_FOUND.AsInteger(): |
| errStr = "JNI method not found"; |
| break; |
| case CHIP_JNI_ERROR_FIELD_NOT_FOUND.AsInteger(): |
| errStr = "JNI field not found"; |
| break; |
| default: |
| errStr = ErrorStr(cbErr); |
| break; |
| } |
| ChipLogError(Controller, "Error in %s : %s", functName, errStr); |
| } |
| } |
| |
| void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| jthrowable ex; |
| |
| err = N2J_Error(env, errToThrow, ex); |
| if (err == CHIP_NO_ERROR) |
| { |
| env->Throw(ex); |
| } |
| } |
| |
| CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| const char * errStr = NULL; |
| jstring errStrObj = NULL; |
| jmethodID constructor; |
| |
| env->ExceptionClear(); |
| constructor = env->GetMethodID(sChipDeviceControllerExceptionCls, "<init>", "(ILjava/lang/String;)V"); |
| VerifyOrExit(constructor != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); |
| |
| switch (inErr.AsInteger()) |
| { |
| case CHIP_JNI_ERROR_TYPE_NOT_FOUND.AsInteger(): |
| errStr = "CHIP Device Controller Error: JNI type not found"; |
| break; |
| case CHIP_JNI_ERROR_METHOD_NOT_FOUND.AsInteger(): |
| errStr = "CHIP Device Controller Error: JNI method not found"; |
| break; |
| case CHIP_JNI_ERROR_FIELD_NOT_FOUND.AsInteger(): |
| errStr = "CHIP Device Controller Error: JNI field not found"; |
| break; |
| case CHIP_JNI_ERROR_DEVICE_NOT_FOUND.AsInteger(): |
| errStr = "CHIP Device Controller Error: Device not found"; |
| break; |
| default: |
| errStr = ErrorStr(inErr); |
| break; |
| } |
| errStrObj = (errStr != NULL) ? env->NewStringUTF(errStr) : NULL; |
| |
| outEx = (jthrowable) env->NewObject(sChipDeviceControllerExceptionCls, constructor, static_cast<jint>(inErr.AsInteger()), |
| errStrObj); |
| VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); |
| |
| exit: |
| env->DeleteLocalRef(errStrObj); |
| return err; |
| } |