blob: 2f75871945797103b1676128d360e5c16bebd394 [file] [log] [blame]
/*
* 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 <jni.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CHIPJNIError.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceConfig.h>
#include <platform/ConfigurationManager.h>
#include <platform/ConnectivityManager.h>
#include <platform/KeyValueStoreManager.h>
#include <platform/internal/BLEManager.h>
#include <android/log.h>
#include "AndroidChipPlatform-JNI.h"
#include "BLEManagerImpl.h"
#include "BleConnectCallback-JNI.h"
#include "CommissionableDataProviderImpl.h"
#include "DiagnosticDataProviderImpl.h"
#include "DnssdImpl.h"
#include "tracing.h"
using namespace chip;
#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_chip_platform_AndroidChipPlatform_##METHOD_NAME
#define JNI_LOGGING_METHOD(RETURN, METHOD_NAME) \
extern "C" JNIEXPORT RETURN JNICALL Java_chip_platform_AndroidChipLogging_##METHOD_NAME
#define JNI_MDNSCALLBACK_METHOD(RETURN, METHOD_NAME) \
extern "C" JNIEXPORT RETURN JNICALL Java_chip_platform_ChipMdnsCallbackImpl_##METHOD_NAME
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
static bool JavaBytesToUUID(JNIEnv * env, jbyteArray value, chip::Ble::ChipBleUUID & uuid);
#endif
namespace {
JavaVM * sJVM = nullptr;
JniGlobalReference sAndroidChipPlatformExceptionCls;
jmethodID sOnLogMessageMethod = nullptr;
JniGlobalReference sJavaLogCallbackObject;
} // namespace
CHIP_ERROR AndroidChipPlatformJNI_OnLoad(JavaVM * jvm, void * reserved)
{
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env;
ChipLogProgress(DeviceLayer, "AndroidChipPlatform JNI_OnLoad() called");
chip::Platform::MemoryInit();
// Save a reference to the JVM. Will need this to call back into Java.
JniReferences::GetInstance().SetJavaVm(jvm, "chip/platform/AndroidChipPlatform");
sJVM = jvm;
// Get a JNI environment object.
env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV);
ChipLogProgress(DeviceLayer, "Loading Java class references.");
// Get various class references need by the API.
jclass androidChipPlatformException;
err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/platform/AndroidChipPlatformException",
androidChipPlatformException);
SuccessOrExit(err);
err = sAndroidChipPlatformExceptionCls.Init(static_cast<jobject>(androidChipPlatformException));
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "Java class references loaded.");
err = BleConnectCallbackJNI_OnLoad(jvm, reserved);
SuccessOrExit(err);
chip::Android::InitializeTracing();
exit:
if (err != CHIP_NO_ERROR)
{
JniReferences::GetInstance().ThrowError(env, sAndroidChipPlatformExceptionCls, err);
JNI_OnUnload(jvm, reserved);
}
return err;
}
void AndroidChipPlatformJNI_OnUnload(JavaVM * jvm, void * reserved)
{
chip::Android::ShutdownTracing();
ChipLogProgress(DeviceLayer, "AndroidChipPlatform JNI_OnUnload() called");
BleConnectCallbackJNI_OnUnload(jvm, reserved);
chip::Platform::MemoryShutdown();
}
JNI_METHOD(void, initChipStack)(JNIEnv * env, jobject self)
{
chip::DeviceLayer::StackLock lock;
CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().InitChipStack();
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "Error initializing CHIP stack: %s", ErrorStr(err)));
}
// for BLEManager
JNI_METHOD(void, nativeSetBLEManager)(JNIEnv *, jobject, jobject manager)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
chip::DeviceLayer::Internal::BLEMgrImpl().InitializeWithObject(manager);
#endif // CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
}
JNI_METHOD(void, handleWriteConfirmation)
(JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
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(DeviceLayer, "handleWriteConfirmation() called with invalid service ID"));
VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID),
ChipLogError(DeviceLayer, "handleWriteConfirmation() called with invalid characteristic ID"));
chip::DeviceLayer::Internal::BLEMgrImpl().HandleWriteConfirmation(connObj, &svcUUID, &charUUID);
#endif
}
JNI_METHOD(void, handleIndicationReceived)
(JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId, jbyteArray value)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
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(DeviceLayer, "handleIndicationReceived() called with invalid service ID"));
VerifyOrExit(JavaBytesToUUID(env, charId, charUUID),
ChipLogError(DeviceLayer, "handleIndicationReceived() called with invalid characteristic ID"));
buffer = System::PacketBufferHandle::NewWithData(valueBegin, valueLength);
VerifyOrExit(!buffer.IsNull(), ChipLogError(DeviceLayer, "Failed to allocate packet buffer"));
chip::DeviceLayer::Internal::BLEMgrImpl().HandleIndicationReceived(connObj, &svcUUID, &charUUID, std::move(buffer));
exit:
env->ReleaseByteArrayElements(value, valueBegin, 0);
#endif
}
JNI_METHOD(void, handleSubscribeComplete)
(JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
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(DeviceLayer, "handleSubscribeComplete() called with invalid service ID"));
VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID),
ChipLogError(DeviceLayer, "handleSubscribeComplete() called with invalid characteristic ID"));
chip::DeviceLayer::Internal::BLEMgrImpl().HandleSubscribeComplete(connObj, &svcUUID, &charUUID);
#endif
}
JNI_METHOD(void, handleUnsubscribeComplete)
(JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
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(DeviceLayer, "handleUnsubscribeComplete() called with invalid service ID"));
VerifyOrReturn(JavaBytesToUUID(env, charId, charUUID),
ChipLogError(DeviceLayer, "handleUnsubscribeComplete() called with invalid characteristic ID"));
chip::DeviceLayer::Internal::BLEMgrImpl().HandleUnsubscribeComplete(connObj, &svcUUID, &charUUID);
#endif
}
JNI_METHOD(void, handleConnectionError)(JNIEnv * env, jobject self, jint conn)
{
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
chip::DeviceLayer::StackLock lock;
BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
chip::DeviceLayer::Internal::BLEMgrImpl().HandleConnectionError(connObj, BLE_ERROR_APP_CLOSED_CONNECTION);
#endif
}
// for KeyValueStoreManager
JNI_METHOD(void, setKeyValueStoreManager)(JNIEnv * env, jclass self, jobject manager)
{
chip::DeviceLayer::StackLock lock;
chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().InitializeWithObject(manager);
}
// for ConfigurationManager
JNI_METHOD(void, setConfigurationManager)(JNIEnv * env, jclass self, jobject manager)
{
chip::DeviceLayer::StackLock lock;
chip::DeviceLayer::ConfigurationManagerImpl::GetDefaultInstance().InitializeWithObject(manager);
}
// for DiagnosticDataProviderManager
JNI_METHOD(void, setDiagnosticDataProviderManager)(JNIEnv * env, jclass self, jobject manager)
{
chip::DeviceLayer::StackLock lock;
chip::DeviceLayer::DiagnosticDataProviderImpl::GetDefaultInstance().InitializeWithObject(manager);
}
// for ServiceResolver and ServiceBrowser
JNI_METHOD(void, nativeSetDnssdDelegates)(JNIEnv * env, jclass self, jobject resolver, jobject browser, jobject chipMdnsCallback)
{
chip::DeviceLayer::StackLock lock;
chip::Dnssd::InitializeWithObjects(resolver, browser, chipMdnsCallback);
}
JNI_LOGGING_METHOD(void, setLogFilter)(JNIEnv * env, jclass clazz, jint level)
{
using namespace chip::Logging;
uint8_t category = kLogCategory_Detail;
switch (level)
{
case ANDROID_LOG_VERBOSE:
case ANDROID_LOG_DEBUG:
category = kLogCategory_Detail;
break;
case ANDROID_LOG_INFO:
category = kLogCategory_Progress;
break;
case ANDROID_LOG_WARN:
case ANDROID_LOG_ERROR:
category = kLogCategory_Error;
break;
default:
break;
}
SetLogFilter(category);
}
static void ENFORCE_FORMAT(3, 0) logRedirectCallback(const char * module, uint8_t category, const char * msg, va_list args)
{
using namespace chip::Logging;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr);
VerifyOrReturn(sJavaLogCallbackObject.HasValidObjectRef());
VerifyOrReturn(sOnLogMessageMethod != nullptr);
JniLocalReferenceScope scope(env);
int priority = ANDROID_LOG_DEBUG;
switch (category)
{
case kLogCategory_Error:
priority = ANDROID_LOG_ERROR;
break;
case kLogCategory_Progress:
priority = ANDROID_LOG_INFO;
break;
case kLogCategory_Detail:
priority = ANDROID_LOG_DEBUG;
break;
default:
break;
}
jint jPriority = static_cast<jint>(priority);
jobject jModule;
VerifyOrReturn(JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString(module), jModule) == CHIP_NO_ERROR);
VerifyOrReturn(jModule != nullptr);
char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE];
vsnprintf(buffer, sizeof(buffer), msg, args);
jobject jMsg;
VerifyOrReturn(JniReferences::GetInstance().CharToStringUTF(CharSpan::fromCharString(buffer), jMsg) == CHIP_NO_ERROR);
VerifyOrReturn(jMsg != nullptr);
env->CallVoidMethod(sJavaLogCallbackObject.ObjectRef(), sOnLogMessageMethod, static_cast<jstring>(jModule), jPriority,
static_cast<jstring>(jMsg));
}
JNI_LOGGING_METHOD(void, setLogCallback)(JNIEnv * env, jclass clazz, jobject callback)
{
using namespace chip::Logging;
if (sOnLogMessageMethod == nullptr)
{
jclass callbackClass = env->GetObjectClass(callback);
sOnLogMessageMethod = env->GetMethodID(callbackClass, "onLogMessage", "(Ljava/lang/String;ILjava/lang/String;)V");
}
VerifyOrReturn(sOnLogMessageMethod != nullptr,
ChipLogError(DeviceLayer, "Failed to access AndroidChipLogging.LogCallback 'onLogMessage' method"));
if (sJavaLogCallbackObject.HasValidObjectRef())
{
sJavaLogCallbackObject.Reset();
}
if (env->IsSameObject(callback, NULL))
{
SetLogRedirectCallback(nullptr);
}
else
{
VerifyOrReturn(sJavaLogCallbackObject.Init(callback) == CHIP_NO_ERROR,
ChipLogError(DeviceLayer, "Failed to init sJavaLogCallbackObject"));
SetLogRedirectCallback(logRedirectCallback);
}
}
JNI_MDNSCALLBACK_METHOD(void, handleServiceResolve)
(JNIEnv * env, jclass self, jstring instanceName, jstring serviceType, jstring hostName, jstring address, jint port,
jobject attributes, jlong callbackHandle, jlong contextHandle)
{
using ::chip::Dnssd::HandleResolve;
HandleResolve(instanceName, serviceType, hostName, address, port, attributes, callbackHandle, contextHandle);
}
JNI_MDNSCALLBACK_METHOD(void, handleServiceBrowse)
(JNIEnv * env, jclass self, jobjectArray instanceName, jstring serviceType, jlong callbackHandle, jlong contextHandle)
{
using ::chip::Dnssd::HandleBrowse;
HandleBrowse(instanceName, serviceType, callbackHandle, contextHandle);
}
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
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;
}
#endif
// for CommissionableDataProvider
JNI_METHOD(jboolean, updateCommissionableDataProviderData)
(JNIEnv * env, jclass self, jstring spake2pVerifierBase64, jstring Spake2pSaltBase64, jint spake2pIterationCount,
jlong setupPasscode, jint discriminator)
{
chip::DeviceLayer::StackLock lock;
CHIP_ERROR err = CommissionableDataProviderMgrImpl().Update(env, spake2pVerifierBase64, Spake2pSaltBase64,
spake2pIterationCount, setupPasscode, discriminator);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Failed to update commissionable data provider data: %s", ErrorStr(err));
return false;
}
return true;
}