diff --git a/kotlin-detect-config.yaml b/kotlin-detect-config.yaml index 4dee3c56abb918..6de03b283f7065 100644 --- a/kotlin-detect-config.yaml +++ b/kotlin-detect-config.yaml @@ -155,6 +155,7 @@ style: - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt" - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/EnterNetworkFragment.kt" - "**/examples/java-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt" + - "**/src/controller/java/src/matter/controller/ReportCallbackJni.kt" - "**/src/controller/java/src/matter/onboardingpayload/Base38.kt" - "**/src/controller/java/generated/java/**/*" ForbiddenComment: @@ -184,6 +185,9 @@ style: excludes: - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt" - "**/src/controller/java/generated/java/**/*" + - "**/src/controller/java/src/matter/controller/Messages.kt" + - "**/src/controller/java/src/matter/controller/model/Paths.kt" + - "**/src/controller/java/src/matter/controller/ReportCallbackJni.kt" - "**/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt" UseCheckOrError: excludes: @@ -205,6 +209,7 @@ style: FunctionOnlyReturningConstant: excludes: - "**/src/controller/java/generated/java/**/*" + - "**/src/controller/java/src/matter/controller/Messages.kt" - "**/src/controller/java/src/matter/onboardingpayload/QRCodeOnboardingPayloadGenerator.kt" exceptions: @@ -301,6 +306,8 @@ complexity: - "**/src/controller/java/src/matter/controller/MatterControllerImpl.kt" - "**/src/controller/java/src/matter/controller/CompletionListenerAdapter.kt" - "**/src/controller/java/src/matter/controller/MatterController.kt" + - "**/src/controller/java/src/matter/controller/ReportCallbackJni.kt" + - "**/src/controller/java/src/matter/controller/model/States.kt" - "**/src/controller/java/tests/matter/jsontlv/JsonToTlvToJsonTest.kt" - "**/src/controller/java/tests/matter/onboardingpayload/ManualCodeTest.kt" - "**/src/controller/java/tests/matter/onboardingpayload/QRCodeTest.kt" @@ -314,12 +321,17 @@ complexity: - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt" - "**/examples/java-matter-controller/java/src/com/matter/controller/commands/common/Command.kt" - "**/src/controller/java/generated/java/**/*" + - "**/src/controller/java/src/matter/controller/MatterControllerImpl.kt" + - "**/src/controller/java/src/matter/controller/InvokeCallbackJni.kt" + - "**/src/controller/java/src/matter/controller/ReportCallbackJni.kt" + - "**/src/controller/java/src/matter/controller/model/States.kt" - "**/src/controller/java/src/matter/onboardingpayload/OnboardingPayload.kt" LongMethod: excludes: - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/AddressCommissioningFragment.kt" - "**/examples/java-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt" - "**/src/controller/java/generated/java/**/*" + - "**/src/controller/java/src/matter/controller/MatterControllerImpl.kt" - "**/src/controller/java/src/matter/onboardingpayload/Base38.kt" - "**/src/controller/java/src/matter/onboardingpayload/ManualOnboardingPayloadGenerator.kt" - "**/src/controller/java/src/matter/onboardingpayload/ManualOnboardingPayloadParser.kt" diff --git a/src/controller/java/AndroidCallbacks-JNI.cpp b/src/controller/java/AndroidCallbacks-JNI.cpp index f0da85a3c6d29e..c87b669f9b521f 100644 --- a/src/controller/java/AndroidCallbacks-JNI.cpp +++ b/src/controller/java/AndroidCallbacks-JNI.cpp @@ -28,66 +28,44 @@ using namespace chip::Controller; JNI_METHOD(jlong, GetConnectedDeviceCallbackJni, newCallback)(JNIEnv * env, jobject self, jobject callback) { - chip::DeviceLayer::StackLock lock; - GetConnectedDeviceCallback * connectedDeviceCallback = chip::Platform::New(self, callback); - return reinterpret_cast(connectedDeviceCallback); + return newConnectedDeviceCallback(env, self, callback); } JNI_METHOD(void, GetConnectedDeviceCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) { - chip::DeviceLayer::StackLock lock; - GetConnectedDeviceCallback * connectedDeviceCallback = reinterpret_cast(callbackHandle); - VerifyOrReturn(connectedDeviceCallback != nullptr, ChipLogError(Controller, "GetConnectedDeviceCallback handle is nullptr")); - chip::Platform::Delete(connectedDeviceCallback); + deleteConnectedDeviceCallback(env, self, callbackHandle); } JNI_METHOD(jlong, ReportCallbackJni, newCallback) -(JNIEnv * env, jobject self, jobject subscriptionEstablishedCallbackJava, jobject reportCallbackJava, - jobject resubscriptionAttemptCallbackJava) +(JNIEnv * env, jobject self, jobject subscriptionEstablishedCallbackJava, jobject resubscriptionAttemptCallbackJava) { - chip::DeviceLayer::StackLock lock; - ReportCallback * reportCallback = chip::Platform::New(self, subscriptionEstablishedCallbackJava, - reportCallbackJava, resubscriptionAttemptCallbackJava); - return reinterpret_cast(reportCallback); + return newReportCallback(env, self, subscriptionEstablishedCallbackJava, resubscriptionAttemptCallbackJava, + "()Lchip/devicecontroller/model/NodeState;"); } JNI_METHOD(void, ReportCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) { - chip::DeviceLayer::StackLock lock; - ReportCallback * reportCallback = reinterpret_cast(callbackHandle); - VerifyOrReturn(reportCallback != nullptr, ChipLogError(Controller, "ReportCallback handle is nullptr")); - chip::Platform::Delete(reportCallback); + deleteReportCallback(env, self, callbackHandle); } JNI_METHOD(jlong, WriteAttributesCallbackJni, newCallback) -(JNIEnv * env, jobject self, jobject writeAttributesCallbackJava) +(JNIEnv * env, jobject self) { - chip::DeviceLayer::StackLock lock; - WriteAttributesCallback * writeAttributesCallback = - chip::Platform::New(self, writeAttributesCallbackJava); - return reinterpret_cast(writeAttributesCallback); + return newWriteAttributesCallback(env, self); } JNI_METHOD(void, WriteAttributesCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) { - chip::DeviceLayer::StackLock lock; - WriteAttributesCallback * writeAttributesCallback = reinterpret_cast(callbackHandle); - VerifyOrReturn(writeAttributesCallback != nullptr, ChipLogError(Controller, "WriteAttributesCallback handle is nullptr")); - chip::Platform::Delete(writeAttributesCallback); + deleteWriteAttributesCallback(env, self, callbackHandle); } JNI_METHOD(jlong, InvokeCallbackJni, newCallback) -(JNIEnv * env, jobject self, jobject invokeCallbackJava) +(JNIEnv * env, jobject self) { - chip::DeviceLayer::StackLock lock; - InvokeCallback * invokeCallback = chip::Platform::New(self, invokeCallbackJava); - return reinterpret_cast(invokeCallback); + return newInvokeCallback(env, self); } JNI_METHOD(void, InvokeCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) { - chip::DeviceLayer::StackLock lock; - InvokeCallback * invokeCallback = reinterpret_cast(callbackHandle); - VerifyOrReturn(invokeCallback != nullptr, ChipLogError(Controller, "InvokeCallback handle is nullptr")); - chip::Platform::Delete(invokeCallback); + deleteInvokeCallback(env, self, callbackHandle); } diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index 9e3e3e966c20d3..cc6107cae3a431 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -41,22 +41,10 @@ static const int MILLIS_SINCE_EPOCH = 1; // Add the bytes for attribute tag(1:control + 8:tag + 8:length) and structure(1:struct + 1:close container) static const int EXTRA_SPACE_FOR_ATTRIBUTE_TAG = 19; -CHIP_ERROR CreateChipAttributePath(JNIEnv * env, const app::ConcreteDataAttributePath & aPath, jobject & outObj) -{ - jclass attributePathCls = nullptr; - ReturnErrorOnFailure( - JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/ChipAttributePath", attributePathCls)); - jmethodID attributePathCtor = - env->GetStaticMethodID(attributePathCls, "newInstance", "(IJJ)Lchip/devicecontroller/model/ChipAttributePath;"); - VerifyOrReturnError(attributePathCtor != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - outObj = env->CallStaticObjectMethod(attributePathCls, attributePathCtor, static_cast(aPath.mEndpointId), - static_cast(aPath.mClusterId), static_cast(aPath.mAttributeId)); - VerifyOrReturnError(outObj != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); - return CHIP_NO_ERROR; -} - -GetConnectedDeviceCallback::GetConnectedDeviceCallback(jobject wrapperCallback, jobject javaCallback) : - mOnSuccess(OnDeviceConnectedFn, this), mOnFailure(OnDeviceConnectionFailureFn, this) +GetConnectedDeviceCallback::GetConnectedDeviceCallback(jobject wrapperCallback, jobject javaCallback, + const char * callbackClassSignature) : + mOnSuccess(OnDeviceConnectedFn, this), + mOnFailure(OnDeviceConnectionFailureFn, this), mCallbackClassSignature(callbackClassSignature) { VerifyOrReturn(mWrapperCallbackRef.Init(wrapperCallback) == CHIP_NO_ERROR, ChipLogError(Controller, "Could not init mWrapperCallbackRef in %s", __func__)); @@ -82,13 +70,15 @@ void GetConnectedDeviceCallback::OnDeviceConnectedFn(void * context, Messaging:: JniGlobalReference globalRef(std::move(self->mWrapperCallbackRef)); jclass getConnectedDeviceCallbackCls = nullptr; - JniReferences::GetInstance().GetLocalClassRef( - env, "chip/devicecontroller/GetConnectedDeviceCallbackJni$GetConnectedDeviceCallback", getConnectedDeviceCallbackCls); + CHIP_ERROR err = + JniReferences::GetInstance().GetLocalClassRef(env, self->mCallbackClassSignature, getConnectedDeviceCallbackCls); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "GetLocalClassRef Error! : %" CHIP_ERROR_FORMAT, err.Format())); VerifyOrReturn(getConnectedDeviceCallbackCls != nullptr, ChipLogError(Controller, "Could not find GetConnectedDeviceCallback class")); jmethodID successMethod; - JniReferences::GetInstance().FindMethod(env, javaCallback, "onDeviceConnected", "(J)V", &successMethod); + err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onDeviceConnected", "(J)V", &successMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "FindMethod Error! : %" CHIP_ERROR_FORMAT, err.Format())); VerifyOrReturn(successMethod != nullptr, ChipLogError(Controller, "Could not find onDeviceConnected method")); static_assert(sizeof(jlong) >= sizeof(void *), "Need to store a pointer in a Java handle"); @@ -111,17 +101,20 @@ void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, JniLocalReferenceScope scope(env); jclass getConnectedDeviceCallbackCls = nullptr; - JniReferences::GetInstance().GetLocalClassRef( - env, "chip/devicecontroller/GetConnectedDeviceCallbackJni$GetConnectedDeviceCallback", getConnectedDeviceCallbackCls); + CHIP_ERROR err = + JniReferences::GetInstance().GetLocalClassRef(env, self->mCallbackClassSignature, getConnectedDeviceCallbackCls); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "GetLocalClassRef Error! : %" CHIP_ERROR_FORMAT, err.Format())); VerifyOrReturn(getConnectedDeviceCallbackCls != nullptr, ChipLogError(Controller, "Could not find GetConnectedDeviceCallback class")); jmethodID failureMethod; - JniReferences::GetInstance().FindMethod(env, javaCallback, "onConnectionFailure", "(JLjava/lang/Exception;)V", &failureMethod); + err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onConnectionFailure", "(JLjava/lang/Exception;)V", + &failureMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "FindMethod Error! : %" CHIP_ERROR_FORMAT, err.Format())); VerifyOrReturn(failureMethod != nullptr, ChipLogError(Controller, "Could not find onConnectionFailure method")); jthrowable exception; - CHIP_ERROR err = AndroidConnectionFailureExceptions::GetInstance().CreateAndroidConnectionFailureException( + err = AndroidConnectionFailureExceptions::GetInstance().CreateAndroidConnectionFailureException( env, failureInfo.error.Format(), failureInfo.error.AsInteger(), failureInfo.sessionStage, exception); VerifyOrReturn( err == CHIP_NO_ERROR, @@ -133,9 +126,29 @@ void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -ReportCallback::ReportCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, jobject reportCallback, - jobject resubscriptionAttemptCallback) : - mClusterCacheAdapter(*this, Optional::Missing(), false /*cacheData*/) +CHIP_ERROR CreateOptional(chip::Optional value, jobject & outObj) +{ + + return CHIP_NO_ERROR; +} +jobject GetNodeStateObj(JNIEnv * env, const char * nodeStateClassSignature, jobject wrapperCallback) +{ + jmethodID getNodeStateMethod; + CHIP_ERROR err = + JniReferences::GetInstance().FindMethod(env, wrapperCallback, "getNodeState", nodeStateClassSignature, &getNodeStateMethod); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, ChipLogError(Controller, "Could not find getNodeState method")); + + DeviceLayer::StackUnlock unlock; + jobject ret = env->CallObjectMethod(wrapperCallback, getNodeStateMethod); + VerifyOrReturnValue(!env->ExceptionCheck(), nullptr, env->ExceptionDescribe()); + + return ret; +} + +ReportCallback::ReportCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, + jobject resubscriptionAttemptCallback, const char * nodeStateClassSignature) : + mClusterCacheAdapter(*this, Optional::Missing(), false /*cacheData*/), + mNodeStateClassSignature(nodeStateClassSignature) { JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); @@ -145,8 +158,6 @@ ReportCallback::ReportCallback(jobject wrapperCallback, jobject subscriptionEsta ChipLogError(Controller, "Could not init mSubscriptionEstablishedCallbackRef in %s", __func__)); } - VerifyOrReturn(mReportCallbackRef.Init(reportCallback) == CHIP_NO_ERROR, - ChipLogError(Controller, "Could not init mReportCallbackRef in %s", __func__)); VerifyOrReturn(mWrapperCallbackRef.Init(wrapperCallback) == CHIP_NO_ERROR, ChipLogError(Controller, "Could not init mWrapperCallbackRef in %s", __func__)); @@ -169,16 +180,19 @@ void ReportCallback::OnReportBegin() { JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); + JniLocalReferenceScope scope(env); - jclass nodeStateCls = nullptr; - CHIP_ERROR err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/NodeState", nodeStateCls); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not get NodeState class")); - jmethodID nodeStateCtor = env->GetMethodID(nodeStateCls, "", "()V"); - VerifyOrReturn(nodeStateCtor != nullptr, ChipLogError(Controller, "Could not find NodeState constructor")); - jobject nodeState = env->NewObject(nodeStateCls, nodeStateCtor); - VerifyOrReturn(nodeState != nullptr, ChipLogError(Controller, "Could not create local object for nodeState")); - VerifyOrReturn(mNodeStateObj.Init(nodeState) == CHIP_NO_ERROR, - ChipLogError(Controller, "Could not init mNodeStateObj in %s", __func__)); + + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + jmethodID onReportBeginMethod; + CHIP_ERROR err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onReportBegin", "()V", &onReportBeginMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onReportBegin method")); + + DeviceLayer::StackUnlock unlock; + env->CallVoidMethod(wrapperCallback, onReportBeginMethod); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } void ReportCallback::OnDeallocatePaths(app::ReadPrepareParams && aReadPrepareParams) @@ -213,18 +227,17 @@ void ReportCallback::OnReportEnd() CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); + JniLocalReferenceScope scope(env); - VerifyOrReturn(mReportCallbackRef.HasValidObjectRef(), - ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); - jobject reportCallback = mReportCallbackRef.ObjectRef(); - JniGlobalReference globalRef(std::move(mNodeStateObj)); - jmethodID onReportMethod; - err = JniReferences::GetInstance().FindMethod(env, reportCallback, "onReport", "(Lchip/devicecontroller/model/NodeState;)V", - &onReportMethod); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onReport method")); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + jmethodID onReportEndMethod; + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onReportEnd", "()V", &onReportEndMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onReportEnd method")); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(reportCallback, onReportMethod, globalRef.ObjectRef()); + env->CallVoidMethod(wrapperCallback, onReportEndMethod); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } @@ -254,30 +267,6 @@ CHIP_ERROR ConvertReportTlvToJson(const uint32_t id, TLV::TLVReader & data, std: return TlvToJson(readerForJson, json); } -static CHIP_ERROR CreateStatus(JNIEnv * env, const app::StatusIB & aStatus, jobject & outObj) -{ - jclass statusCls = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/Status", statusCls)); - jmethodID statusCtor = nullptr; - if (aStatus.mClusterStatus.HasValue()) - { - statusCtor = env->GetStaticMethodID(statusCls, "newInstance", "(II)Lchip/devicecontroller/model/Status;"); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrReturnError(statusCtor != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - outObj = env->CallStaticObjectMethod(statusCls, statusCtor, static_cast(aStatus.mStatus), - static_cast(aStatus.mClusterStatus.Value())); - } - else - { - statusCtor = env->GetStaticMethodID(statusCls, "newInstance", "(I)Lchip/devicecontroller/model/Status;"); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrReturnError(statusCtor != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - outObj = env->CallStaticObjectMethod(statusCls, statusCtor, static_cast(aStatus.mStatus)); - } - VerifyOrReturnError(outObj != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - return CHIP_NO_ERROR; -} - void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) { @@ -287,23 +276,34 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat JniLocalReferenceScope scope(env); VerifyOrReturn(!aPath.IsListItemOperation(), ChipLogError(Controller, "Expect non-list item operation"); aPath.LogPath()); - jobject nodeState = mNodeStateObj.ObjectRef(); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + + jobject nodeState = GetNodeStateObj(env, mNodeStateClassSignature, wrapperCallback); if (aStatus.IsFailure()) { ChipLogError(Controller, "Receive bad status %s", ErrorStr(aStatus.ToChipError())); - jobject statusObj = nullptr; - err = CreateStatus(env, aStatus, statusObj); - VerifyOrReturn(err == CHIP_NO_ERROR, - ChipLogError(Controller, "Fail to create status with error %" CHIP_ERROR_FORMAT, err.Format())); - // Add Attribute Status to NodeState + // Add Attribute Status to wrapperCallback jmethodID addAttributeStatusMethod = nullptr; - err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttributeStatus", - "(IJJLchip/devicecontroller/model/Status;)V", &addAttributeStatusMethod); + err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttributeStatus", "(IJJILjava/lang/Integer;)V", + &addAttributeStatusMethod); VerifyOrReturn( err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addAttributeStatus method with error %" CHIP_ERROR_FORMAT, err.Format())); + + jobject jClusterState = nullptr; + if (aStatus.mClusterStatus.HasValue()) + { + err = JniReferences::GetInstance().CreateBoxedObject( + "java/lang/Integer", "(I)V", static_cast(aStatus.mClusterStatus.Value()), jClusterState); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(Controller, "Could not CreateBoxedObject with error %" CHIP_ERROR_FORMAT, err.Format())); + } + env->CallVoidMethod(nodeState, addAttributeStatusMethod, static_cast(aPath.mEndpointId), - static_cast(aPath.mClusterId), static_cast(aPath.mAttributeId), statusObj); + static_cast(aPath.mClusterId), static_cast(aPath.mAttributeId), + static_cast(aStatus.mStatus), jClusterState); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); return; } @@ -352,29 +352,14 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat aPath.LogPath()); UtfString jsonString(env, json.c_str()); - // Create AttributeState object - jclass attributeStateCls; - err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/AttributeState", attributeStateCls); - VerifyOrReturn(err == CHIP_NO_ERROR, - ChipLogError(Controller, "Could not find AttributeState class with error %s", ErrorStr(err)); - aPath.LogPath()); - VerifyOrReturn(attributeStateCls != nullptr, ChipLogError(Controller, "Could not find AttributeState class"); aPath.LogPath()); - jmethodID attributeStateCtor = env->GetMethodID(attributeStateCls, "", "(Ljava/lang/Object;[BLjava/lang/String;)V"); - VerifyOrReturn(attributeStateCtor != nullptr, ChipLogError(Controller, "Could not find AttributeState constructor"); - aPath.LogPath()); - jobject attributeStateObj = - env->NewObject(attributeStateCls, attributeStateCtor, value, jniByteArray.jniValue(), jsonString.jniValue()); - VerifyOrReturn(attributeStateObj != nullptr, ChipLogError(Controller, "Could not create AttributeState object"); - aPath.LogPath()); - - // Add AttributeState to NodeState + // Add AttributeState to wrapperCallback jmethodID addAttributeMethod; - err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttribute", - "(IJJLchip/devicecontroller/model/AttributeState;)V", &addAttributeMethod); + err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttribute", "(IJJLjava/lang/Object;[BLjava/lang/String;)V", + &addAttributeMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addAttribute method with error %s", ErrorStr(err))); env->CallVoidMethod(nodeState, addAttributeMethod, static_cast(aPath.mEndpointId), static_cast(aPath.mClusterId), - static_cast(aPath.mAttributeId), attributeStateObj); + static_cast(aPath.mAttributeId), value, jniByteArray.jniValue(), jsonString.jniValue()); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); UpdateClusterDataVersion(); @@ -406,7 +391,10 @@ void ReportCallback::UpdateClusterDataVersion() return; } - jobject nodeState = mNodeStateObj.ObjectRef(); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + jobject nodeState = GetNodeStateObj(env, mNodeStateClassSignature, wrapperCallback); // SetDataVersion to NodeState jmethodID setDataVersionMethod; @@ -423,24 +411,33 @@ void ReportCallback::OnEventData(const app::EventHeader & aEventHeader, TLV::TLV JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); - jobject nodeState = mNodeStateObj.ObjectRef(); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + jobject nodeState = GetNodeStateObj(env, mNodeStateClassSignature, wrapperCallback); if (apStatus != nullptr && apStatus->IsFailure()) { ChipLogError(Controller, "Receive bad status %s", ErrorStr(apStatus->ToChipError())); - jobject statusObj = nullptr; - err = CreateStatus(env, *apStatus, statusObj); - VerifyOrReturn(err == CHIP_NO_ERROR, - ChipLogError(Controller, "Fail to create status with error %" CHIP_ERROR_FORMAT, err.Format())); // Add Event Status to NodeState jmethodID addEventStatusMethod; - err = JniReferences::GetInstance().FindMethod(env, nodeState, "addEventStatus", - "(IJJLchip/devicecontroller/model/Status;)V", &addEventStatusMethod); + err = JniReferences::GetInstance().FindMethod(env, nodeState, "addEventStatus", "(IJJILjava/lang/Integer;)V", + &addEventStatusMethod); VerifyOrReturn( err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addEventStatus method with error %" CHIP_ERROR_FORMAT, err.Format())); + + jobject jClusterState = nullptr; + if (apStatus->mClusterStatus.HasValue()) + { + err = JniReferences::GetInstance().CreateBoxedObject( + "java/lang/Integer", "(I)V", static_cast(apStatus->mClusterStatus.Value()), jClusterState); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(Controller, "Could not CreateBoxedObject with error %" CHIP_ERROR_FORMAT, err.Format())); + } + env->CallVoidMethod(nodeState, addEventStatusMethod, static_cast(aEventHeader.mPath.mEndpointId), static_cast(aEventHeader.mPath.mClusterId), static_cast(aEventHeader.mPath.mEventId), - statusObj); + static_cast(apStatus->mStatus), jClusterState); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); return; } @@ -508,86 +505,18 @@ void ReportCallback::OnEventData(const app::EventHeader & aEventHeader, TLV::TLV aEventHeader.LogPath()); UtfString jsonString(env, json.c_str()); - // Create EventState object - jclass eventStateCls; - err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/EventState", eventStateCls); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed to find EventState class"); aEventHeader.LogPath()); - VerifyOrReturn(eventStateCls != nullptr, ChipLogError(Controller, "Could not find EventState class"); aEventHeader.LogPath()); - jmethodID eventStateCtor = env->GetMethodID(eventStateCls, "", "(JIIJLjava/lang/Object;[BLjava/lang/String;)V"); - VerifyOrReturn(eventStateCtor != nullptr, ChipLogError(Controller, "Could not find EventState constructor"); - aEventHeader.LogPath()); - jobject eventStateObj = env->NewObject(eventStateCls, eventStateCtor, eventNumber, priorityLevel, timestampType, timestampValue, - value, jniByteArray.jniValue(), jsonString.jniValue()); - VerifyOrReturn(eventStateObj != nullptr, ChipLogError(Controller, "Could not create EventState object"); - aEventHeader.LogPath()); - - // Add EventState to NodeState jmethodID addEventMethod; - - err = JniReferences::GetInstance().FindMethod(env, nodeState, "addEvent", "(IJJLchip/devicecontroller/model/EventState;)V", + err = JniReferences::GetInstance().FindMethod(env, nodeState, "addEvent", "(IJJJIIJLjava/lang/Object;[BLjava/lang/String;)V", &addEventMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addEvent method with error %s", ErrorStr(err)); aEventHeader.LogPath()); env->CallVoidMethod(nodeState, addEventMethod, static_cast(aEventHeader.mPath.mEndpointId), static_cast(aEventHeader.mPath.mClusterId), static_cast(aEventHeader.mPath.mEventId), - eventStateObj); + eventNumber, priorityLevel, timestampType, timestampValue, value, jniByteArray.jniValue(), + jsonString.jniValue()); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe(); aEventHeader.LogPath()); } -CHIP_ERROR InvokeCallback::CreateInvokeElement(JNIEnv * env, const app::ConcreteCommandPath & aPath, TLV::TLVReader * apData, - jobject & outObj) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - jclass invokeElementCls = nullptr; - err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/InvokeElement", invokeElementCls); - ReturnErrorOnFailure(err); - - jmethodID invokeElementCtor = env->GetStaticMethodID(invokeElementCls, "newInstance", - "(IJJ[BLjava/lang/String;)Lchip/devicecontroller/model/InvokeElement;"); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrReturnError(invokeElementCtor != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); - - if (apData != nullptr) - { - TLV::TLVReader readerForJavaTLV; - TLV::TLVReader readerForJson; - readerForJavaTLV.Init(*apData); - - // Create TLV byte array to pass to Java layer - size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead(); - - std::unique_ptr buffer = std::unique_ptr(new uint8_t[bufferLen]); - uint32_t size = 0; - - TLV::TLVWriter writer; - writer.Init(buffer.get(), bufferLen); - err = writer.CopyElement(TLV::AnonymousTag(), readerForJavaTLV); - ReturnErrorOnFailure(err); - size = writer.GetLengthWritten(); - chip::ByteArray jniByteArray(env, reinterpret_cast(buffer.get()), static_cast(size)); - - // Convert TLV to JSON - std::string json; - readerForJson.Init(buffer.get(), size); - err = readerForJson.Next(); - ReturnErrorOnFailure(err); - err = TlvToJson(readerForJson, json); - ReturnErrorOnFailure(err); - UtfString jsonString(env, json.c_str()); - outObj = env->CallStaticObjectMethod(invokeElementCls, invokeElementCtor, static_cast(aPath.mEndpointId), - static_cast(aPath.mClusterId), static_cast(aPath.mCommandId), - jniByteArray.jniValue(), jsonString.jniValue()); - } - else - { - outObj = env->CallStaticObjectMethod(invokeElementCls, invokeElementCtor, static_cast(aPath.mEndpointId), - static_cast(aPath.mClusterId), static_cast(aPath.mCommandId), nullptr, - nullptr); - } - VerifyOrReturnError(outObj != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); - return CHIP_NO_ERROR; -} - void ReportCallback::OnError(CHIP_ERROR aError) { ReportError(nullptr, nullptr, aError); @@ -601,12 +530,11 @@ void ReportCallback::OnDone(app::ReadClient *) JniLocalReferenceScope scope(env); VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); - JniGlobalReference globalRef(std::move(mWrapperCallbackRef)); jmethodID onDoneMethod; - VerifyOrReturn(mReportCallbackRef.HasValidObjectRef(), - ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); - jobject reportCallback = mReportCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, reportCallback, "onDone", "()V", &onDoneMethod); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + JniGlobalReference globalRef(std::move(mWrapperCallbackRef)); + + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onDone", "()V", &onDoneMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onDone method")); if (mReadClient != nullptr) @@ -616,7 +544,7 @@ void ReportCallback::OnDone(app::ReadClient *) mReadClient = nullptr; DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(reportCallback, onDoneMethod); + env->CallVoidMethod(wrapperCallback, onDoneMethod); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } @@ -628,7 +556,10 @@ void ReportCallback::OnSubscriptionEstablished(SubscriptionId aSubscriptionId) DeviceLayer::StackUnlock unlock; VerifyOrReturn(mSubscriptionEstablishedCallbackRef.HasValidObjectRef(), ChipLogError(Controller, " mSubscriptionEstablishedCallbackRef is not valid in %s", __func__)); - JniReferences::GetInstance().CallSubscriptionEstablished(mSubscriptionEstablishedCallbackRef.ObjectRef(), aSubscriptionId); + CHIP_ERROR err = + JniReferences::GetInstance().CallSubscriptionEstablished(mSubscriptionEstablishedCallbackRef.ObjectRef(), aSubscriptionId); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(Controller, "CallSubscriptionEstablished error! : %" CHIP_ERROR_FORMAT, err.Format())); } CHIP_ERROR ReportCallback::OnResubscriptionNeeded(app::ReadClient * apReadClient, CHIP_ERROR aTerminationCause) @@ -651,49 +582,76 @@ CHIP_ERROR ReportCallback::OnResubscriptionNeeded(app::ReadClient * apReadClient return CHIP_NO_ERROR; } -void ReportCallback::ReportError(jobject attributePath, jobject eventPath, CHIP_ERROR err) +void ReportCallback::ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, + CHIP_ERROR err) { ReportError(attributePath, eventPath, ErrorStr(err), err.AsInteger()); } -void ReportCallback::ReportError(jobject attributePath, jobject eventPath, Protocols::InteractionModel::Status status) +void ReportCallback::ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, + Protocols::InteractionModel::Status status) { ReportError(attributePath, eventPath, "IM Status", static_cast>(status)); } -void ReportCallback::ReportError(jobject attributePath, jobject eventPath, const char * message, ChipError::StorageType errorCode) +void ReportCallback::ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, + const char * message, ChipError::StorageType errorCode) { CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); JniLocalReferenceScope scope(env); - VerifyOrReturn(mReportCallbackRef.HasValidObjectRef(), - ChipLogError(Controller, "mReportCallbackRef is not valid in %s", __func__)); - jobject reportCallback = mReportCallbackRef.ObjectRef(); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); jthrowable exception; err = AndroidControllerExceptions::GetInstance().CreateAndroidControllerException(env, message, errorCode, exception); VerifyOrReturn( err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create AndroidControllerException on ReportCallback::ReportError: %s", ErrorStr(err))); jmethodID onErrorMethod; - err = JniReferences::GetInstance().FindMethod( - env, reportCallback, "onError", - "(Lchip/devicecontroller/model/ChipAttributePath;Lchip/devicecontroller/model/ChipEventPath;Ljava/lang/Exception;)V", - &onErrorMethod); + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onError", "(ZIJJZIJJLjava/lang/Exception;)V", + &onErrorMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(reportCallback, onErrorMethod, attributePath, eventPath, exception); + + jboolean isAttributePath = JNI_FALSE; + jint attributeEndpointId = static_cast(kInvalidEndpointId); + jlong attributeClusterId = static_cast(kInvalidClusterId); + jlong attributeId = static_cast(kInvalidAttributeId); + + jboolean isEventPath = JNI_FALSE; + jint eventEndpointId = static_cast(kInvalidEndpointId); + jlong eventClusterId = static_cast(kInvalidClusterId); + jlong eventId = static_cast(kInvalidAttributeId); + + if (attributePath != nullptr) + { + isAttributePath = JNI_TRUE; + attributeEndpointId = static_cast(attributePath->mEndpointId); + attributeClusterId = static_cast(attributePath->mClusterId); + attributeId = static_cast(attributePath->mAttributeId); + } + + if (eventPath != nullptr) + { + isEventPath = JNI_TRUE; + eventEndpointId = static_cast(eventPath->mEndpointId); + eventClusterId = static_cast(eventPath->mClusterId); + eventId = static_cast(eventPath->mEventId); + } + + env->CallVoidMethod(wrapperCallback, onErrorMethod, isAttributePath, attributeEndpointId, attributeClusterId, attributeId, + isEventPath, eventEndpointId, eventClusterId, eventId, exception); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -WriteAttributesCallback::WriteAttributesCallback(jobject wrapperCallback, jobject javaCallback) : mChunkedWriteCallback(this) +WriteAttributesCallback::WriteAttributesCallback(jobject wrapperCallback) : mChunkedWriteCallback(this) { VerifyOrReturn(mWrapperCallbackRef.Init(wrapperCallback) == CHIP_NO_ERROR, ChipLogError(Controller, "Could not init mWrapperCallbackRef for WriteAttributesCallback")); - VerifyOrReturn(mJavaCallbackRef.Init(javaCallback) == CHIP_NO_ERROR, - ChipLogError(Controller, "Could not init mJavaCallbackRef in %s", __func__)); } WriteAttributesCallback::~WriteAttributesCallback() @@ -712,25 +670,22 @@ void WriteAttributesCallback::OnResponse(const app::WriteClient * apWriteClient, VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); JniLocalReferenceScope scope(env); - jobject attributePathObj = nullptr; - err = CreateChipAttributePath(env, aPath, attributePathObj); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create Java ChipAttributePath: %s", ErrorStr(err))); - if (aStatus.mStatus != Protocols::InteractionModel::Status::Success) { - ReportError(attributePathObj, aStatus.mStatus); + ReportError(&aPath, aStatus.mStatus); return; } jmethodID onResponseMethod; - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onResponse", - "(Lchip/devicecontroller/model/ChipAttributePath;)V", &onResponseMethod); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onResponse", "(IJJ)V", &onResponseMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, onResponseMethod, attributePathObj); + env->CallVoidMethod(wrapperCallback, onResponseMethod, static_cast(aPath.mEndpointId), + static_cast(aPath.mClusterId), static_cast(aPath.mAttributeId)); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } @@ -747,29 +702,31 @@ void WriteAttributesCallback::OnDone(app::WriteClient *) JniLocalReferenceScope scope(env); VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); - JniGlobalReference globalRef(std::move(mWrapperCallbackRef)); jmethodID onDoneMethod; - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onDone", "()V", &onDoneMethod); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + JniGlobalReference globalRef(std::move(mWrapperCallbackRef)); + + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onDone", "()V", &onDoneMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onDone method")); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, onDoneMethod); + env->CallVoidMethod(wrapperCallback, onDoneMethod); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -void WriteAttributesCallback::ReportError(jobject attributePath, CHIP_ERROR err) +void WriteAttributesCallback::ReportError(const app::ConcreteAttributePath * attributePath, CHIP_ERROR err) { ReportError(attributePath, ErrorStr(err), err.AsInteger()); } -void WriteAttributesCallback::ReportError(jobject attributePath, Protocols::InteractionModel::Status status) +void WriteAttributesCallback::ReportError(const app::ConcreteAttributePath * attributePath, + Protocols::InteractionModel::Status status) { ReportError(attributePath, "IM Status", static_cast>(status)); } -void WriteAttributesCallback::ReportError(jobject attributePath, const char * message, ChipError::StorageType errorCode) +void WriteAttributesCallback::ReportError(const app::ConcreteAttributePath * attributePath, const char * message, + ChipError::StorageType errorCode) { CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); @@ -783,23 +740,35 @@ void WriteAttributesCallback::ReportError(jobject attributePath, const char * me "Unable to create AndroidControllerException on WriteAttributesCallback::ReportError: %s", ErrorStr(err))); jmethodID onErrorMethod; - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod( - env, javaCallback, "onError", "(Lchip/devicecontroller/model/ChipAttributePath;Ljava/lang/Exception;)V", &onErrorMethod); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onError", "(ZIJJLjava/lang/Exception;)V", &onErrorMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); + jboolean isAttributePath = JNI_FALSE; + jint attributeEndpointId = static_cast(kInvalidEndpointId); + jlong attributeClusterId = static_cast(kInvalidClusterId); + jlong attributeId = static_cast(kInvalidAttributeId); + + if (attributePath != nullptr) + { + isAttributePath = JNI_TRUE; + attributeEndpointId = static_cast(attributePath->mEndpointId); + attributeClusterId = static_cast(attributePath->mClusterId); + attributeId = static_cast(attributePath->mAttributeId); + } + DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, onErrorMethod, attributePath, exception); + env->CallVoidMethod(wrapperCallback, onErrorMethod, isAttributePath, attributeEndpointId, attributeClusterId, attributeId, + exception); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -InvokeCallback::InvokeCallback(jobject wrapperCallback, jobject javaCallback) +InvokeCallback::InvokeCallback(jobject wrapperCallback) { VerifyOrReturn(mWrapperCallbackRef.Init(wrapperCallback) == CHIP_NO_ERROR, ChipLogError(Controller, "Could not init mWrapperCallbackRef for InvokeCallback")); - VerifyOrReturn(mJavaCallbackRef.Init(javaCallback) == CHIP_NO_ERROR, - ChipLogError(Controller, "Could not init mJavaCallbackRef in %s", __func__)); } InvokeCallback::~InvokeCallback() @@ -816,27 +785,61 @@ void InvokeCallback::OnResponse(app::CommandSender * apCommandSender, const app: CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); - jobject invokeElementObj = nullptr; jmethodID onResponseMethod; JniLocalReferenceScope scope(env); - err = CreateInvokeElement(env, aPath, apData, invokeElementObj); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create Java InvokeElement: %s", ErrorStr(err))); - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onResponse", - "(Lchip/devicecontroller/model/InvokeElement;J)V", &onResponseMethod); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallbackRef = mWrapperCallbackRef.ObjectRef(); + err = JniReferences::GetInstance().FindMethod(env, wrapperCallbackRef, "onResponse", "(IJJ[BLjava/lang/String;J)V", + &onResponseMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onResponse method: %s", ErrorStr(err))); DeviceLayer::StackUnlock unlock; - if (aStatusIB.mClusterStatus.HasValue()) + + if (apData != nullptr) { - env->CallVoidMethod(javaCallback, onResponseMethod, invokeElementObj, static_cast(aStatusIB.mClusterStatus.Value())); + TLV::TLVReader readerForJavaTLV; + TLV::TLVReader readerForJson; + readerForJavaTLV.Init(*apData); + + // Create TLV byte array to pass to Java layer + size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead(); + std::unique_ptr buffer = std::unique_ptr(new uint8_t[bufferLen]); + uint32_t size = 0; + + TLV::TLVWriter writer; + writer.Init(buffer.get(), bufferLen); + err = writer.CopyElement(TLV::AnonymousTag(), readerForJavaTLV); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed CopyElement: %" CHIP_ERROR_FORMAT, err.Format())); + size = writer.GetLengthWritten(); + + chip::ByteArray jniByteArray(env, reinterpret_cast(buffer.get()), static_cast(size)); + + // Convert TLV to JSON + std::string json; + readerForJson.Init(buffer.get(), size); + err = readerForJson.Next(); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(Controller, "Failed readerForJson next: %" CHIP_ERROR_FORMAT, err.Format())); + err = TlvToJson(readerForJson, json); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed TlvToJson: %" CHIP_ERROR_FORMAT, err.Format())); + UtfString jsonString(env, json.c_str()); + + env->CallVoidMethod(wrapperCallbackRef, onResponseMethod, static_cast(aPath.mEndpointId), + static_cast(aPath.mClusterId), static_cast(aPath.mCommandId), jniByteArray.jniValue(), + jsonString.jniValue(), + aStatusIB.mClusterStatus.HasValue() ? static_cast(aStatusIB.mClusterStatus.Value()) + : static_cast(Protocols::InteractionModel::Status::Success)); } else { - env->CallVoidMethod(javaCallback, onResponseMethod, invokeElementObj, - static_cast(Protocols::InteractionModel::Status::Success)); + env->CallVoidMethod(wrapperCallbackRef, onResponseMethod, static_cast(aPath.mEndpointId), + static_cast(aPath.mClusterId), static_cast(aPath.mCommandId), nullptr, nullptr, + aStatusIB.mClusterStatus.HasValue() ? static_cast(aStatusIB.mClusterStatus.Value()) + : static_cast(Protocols::InteractionModel::Status::Success)); } + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } @@ -851,17 +854,17 @@ void InvokeCallback::OnDone(app::CommandSender * apCommandSender) JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); JniLocalReferenceScope scope(env); + jmethodID onDoneMethod; VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); JniGlobalReference globalRef(std::move(mWrapperCallbackRef)); - jmethodID onDoneMethod; - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onDone", "()V", &onDoneMethod); + + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onDone", "()V", &onDoneMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onDone method")); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, onDoneMethod); + env->CallVoidMethod(wrapperCallback, onDoneMethod); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } @@ -889,15 +892,78 @@ void InvokeCallback::ReportError(const char * message, ChipError::StorageType er ChipLogError(Controller, "Unable to create AndroidControllerException: %s on InvokeCallback::ReportError", ErrorStr(err))); jmethodID onErrorMethod; - VerifyOrReturn(mJavaCallbackRef.HasValidObjectRef(), ChipLogError(Controller, "mJavaCallbackRef is not valid in %s", __func__)); - jobject javaCallback = mJavaCallbackRef.ObjectRef(); - err = JniReferences::GetInstance().FindMethod(env, javaCallback, "onError", "(Ljava/lang/Exception;)V", &onErrorMethod); + VerifyOrReturn(mWrapperCallbackRef.HasValidObjectRef(), + ChipLogError(Controller, "mWrapperCallbackRef is not valid in %s", __func__)); + jobject wrapperCallback = mWrapperCallbackRef.ObjectRef(); + err = JniReferences::GetInstance().FindMethod(env, wrapperCallback, "onError", "(Ljava/lang/Exception;)V", &onErrorMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(javaCallback, onErrorMethod, exception); + env->CallVoidMethod(wrapperCallback, onErrorMethod, exception); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } +jlong newConnectedDeviceCallback(JNIEnv * env, jobject self, jobject callback) +{ + chip::DeviceLayer::StackLock lock; + GetConnectedDeviceCallback * connectedDeviceCallback = chip::Platform::New(self, callback); + return reinterpret_cast(connectedDeviceCallback); +} + +void deleteConnectedDeviceCallback(JNIEnv * env, jobject self, jlong callbackHandle) +{ + chip::DeviceLayer::StackLock lock; + GetConnectedDeviceCallback * connectedDeviceCallback = reinterpret_cast(callbackHandle); + VerifyOrReturn(connectedDeviceCallback != nullptr, ChipLogError(Controller, "GetConnectedDeviceCallback handle is nullptr")); + chip::Platform::Delete(connectedDeviceCallback); +} + +jlong newReportCallback(JNIEnv * env, jobject self, jobject subscriptionEstablishedCallbackJava, + jobject resubscriptionAttemptCallbackJava, const char * nodeStateClassSignature) +{ + chip::DeviceLayer::StackLock lock; + ReportCallback * reportCallback = chip::Platform::New( + self, subscriptionEstablishedCallbackJava, resubscriptionAttemptCallbackJava, nodeStateClassSignature); + return reinterpret_cast(reportCallback); +} + +void deleteReportCallback(JNIEnv * env, jobject self, jlong callbackHandle) +{ + chip::DeviceLayer::StackLock lock; + ReportCallback * reportCallback = reinterpret_cast(callbackHandle); + VerifyOrReturn(reportCallback != nullptr, ChipLogError(Controller, "ReportCallback handle is nullptr")); + chip::Platform::Delete(reportCallback); +} + +jlong newWriteAttributesCallback(JNIEnv * env, jobject self) +{ + chip::DeviceLayer::StackLock lock; + WriteAttributesCallback * writeAttributesCallback = chip::Platform::New(self); + return reinterpret_cast(writeAttributesCallback); +} + +void deleteWriteAttributesCallback(JNIEnv * env, jobject self, jlong callbackHandle) +{ + chip::DeviceLayer::StackLock lock; + WriteAttributesCallback * writeAttributesCallback = reinterpret_cast(callbackHandle); + VerifyOrReturn(writeAttributesCallback != nullptr, ChipLogError(Controller, "WriteAttributesCallback handle is nullptr")); + chip::Platform::Delete(writeAttributesCallback); +} + +jlong newInvokeCallback(JNIEnv * env, jobject self) +{ + chip::DeviceLayer::StackLock lock; + InvokeCallback * invokeCallback = chip::Platform::New(self); + return reinterpret_cast(invokeCallback); +} + +void deleteInvokeCallback(JNIEnv * env, jobject self, jlong callbackHandle) +{ + chip::DeviceLayer::StackLock lock; + InvokeCallback * invokeCallback = reinterpret_cast(callbackHandle); + VerifyOrReturn(invokeCallback != nullptr, ChipLogError(Controller, "InvokeCallback handle is nullptr")); + chip::Platform::Delete(invokeCallback); +} + } // namespace Controller } // namespace chip diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h index f00075258bea8f..700a1fb467f13f 100644 --- a/src/controller/java/AndroidCallbacks.h +++ b/src/controller/java/AndroidCallbacks.h @@ -35,7 +35,9 @@ CHIP_ERROR CreateChipAttributePath(JNIEnv * env, const app::ConcreteDataAttribut // Callback for success and failure cases of GetConnectedDevice(). struct GetConnectedDeviceCallback { - GetConnectedDeviceCallback(jobject wrapperCallback, jobject javaCallback); + GetConnectedDeviceCallback( + jobject wrapperCallback, jobject javaCallback, + const char * callbackClassSignature = "chip/devicecontroller/GetConnectedDeviceCallbackJni$GetConnectedDeviceCallback"); ~GetConnectedDeviceCallback(); static void OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle); @@ -45,13 +47,16 @@ struct GetConnectedDeviceCallback Callback::Callback mOnFailure; JniGlobalReference mWrapperCallbackRef; JniGlobalReference mJavaCallbackRef; + +private: + const char * mCallbackClassSignature = nullptr; }; struct ReportCallback : public app::ClusterStateCache::Callback { /** Subscription established callback can be nullptr. */ - ReportCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, jobject reportCallback, - jobject resubscriptionAttemptCallback); + ReportCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, jobject resubscriptionAttemptCallback, + const char * nodeStateClassSignature); ~ReportCallback(); void OnReportBegin() override; @@ -73,11 +78,11 @@ struct ReportCallback : public app::ClusterStateCache::Callback void OnDeallocatePaths(app::ReadPrepareParams && aReadPrepareParams) override; /** Report errors back to Java layer. attributePath may be nullptr for general errors. */ - void ReportError(jobject attributePath, jobject eventPath, CHIP_ERROR err); - void ReportError(jobject attributePath, jobject eventPath, Protocols::InteractionModel::Status status); - void ReportError(jobject attributePath, jobject eventPath, const char * message, ChipError::StorageType errorCode); - - CHIP_ERROR CreateChipEventPath(JNIEnv * env, const app::ConcreteEventPath & aPath, jobject & outObj); + void ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, CHIP_ERROR err); + void ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, + Protocols::InteractionModel::Status status); + void ReportError(const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, + const char * message, ChipError::StorageType errorCode); void UpdateClusterDataVersion(); @@ -87,14 +92,13 @@ struct ReportCallback : public app::ClusterStateCache::Callback JniGlobalReference mWrapperCallbackRef; JniGlobalReference mSubscriptionEstablishedCallbackRef; JniGlobalReference mResubscriptionAttemptCallbackRef; - JniGlobalReference mReportCallbackRef; - // NodeState Java object that will be returned to the application. - JniGlobalReference mNodeStateObj; + + const char * mNodeStateClassSignature; }; struct WriteAttributesCallback : public app::WriteClient::Callback { - WriteAttributesCallback(jobject wrapperCallback, jobject javaCallback); + WriteAttributesCallback(jobject wrapperCallback); ~WriteAttributesCallback(); app::WriteClient::Callback * GetChunkedWriteCallback() { return &mChunkedWriteCallback; } @@ -105,19 +109,18 @@ struct WriteAttributesCallback : public app::WriteClient::Callback void OnDone(app::WriteClient * apWriteClient) override; - void ReportError(jobject attributePath, CHIP_ERROR err); - void ReportError(jobject attributePath, Protocols::InteractionModel::Status status); - void ReportError(jobject attributePath, const char * message, ChipError::StorageType errorCode); + void ReportError(const app::ConcreteAttributePath * attributePath, CHIP_ERROR err); + void ReportError(const app::ConcreteAttributePath * attributePath, Protocols::InteractionModel::Status status); + void ReportError(const app::ConcreteAttributePath * attributePath, const char * message, ChipError::StorageType errorCode); app::WriteClient * mWriteClient = nullptr; app::ChunkedWriteCallback mChunkedWriteCallback; JniGlobalReference mWrapperCallbackRef; - JniGlobalReference mJavaCallbackRef; }; struct InvokeCallback : public app::CommandSender::Callback { - InvokeCallback(jobject wrapperCallback, jobject javaCallback); + InvokeCallback(jobject wrapperCallback); ~InvokeCallback(); void OnResponse(app::CommandSender * apCommandSender, const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatusIB, @@ -127,15 +130,23 @@ struct InvokeCallback : public app::CommandSender::Callback void OnDone(app::CommandSender * apCommandSender) override; - CHIP_ERROR CreateInvokeElement(JNIEnv * env, const app::ConcreteCommandPath & aPath, TLV::TLVReader * apData, jobject & outObj); void ReportError(CHIP_ERROR err); void ReportError(Protocols::InteractionModel::Status status); void ReportError(const char * message, ChipError::StorageType errorCode); app::CommandSender * mCommandSender = nullptr; JniGlobalReference mWrapperCallbackRef; - JniGlobalReference mJavaCallbackRef; }; +jlong newConnectedDeviceCallback(JNIEnv * env, jobject self, jobject callback); +void deleteConnectedDeviceCallback(JNIEnv * env, jobject self, jlong callbackHandle); +jlong newReportCallback(JNIEnv * env, jobject self, jobject subscriptionEstablishedCallbackJava, + jobject resubscriptionAttemptCallbackJava, const char * nodeStateClassSignature); +void deleteReportCallback(JNIEnv * env, jobject self, jlong callbackHandle); +jlong newWriteAttributesCallback(JNIEnv * env, jobject self); +void deleteWriteAttributesCallback(JNIEnv * env, jobject self, jlong callbackHandle); +jlong newInvokeCallback(JNIEnv * env, jobject self); +void deleteInvokeCallback(JNIEnv * env, jobject self, jlong callbackHandle); + } // namespace Controller } // namespace chip diff --git a/src/controller/java/AndroidInteractionClient.cpp b/src/controller/java/AndroidInteractionClient.cpp new file mode 100644 index 00000000000000..a0b22bfde530af --- /dev/null +++ b/src/controller/java/AndroidInteractionClient.cpp @@ -0,0 +1,745 @@ +/* + * Copyright (c) 2024 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 Interaction Client API for Android Platform + * + */ +#include "AndroidInteractionClient.h" + +#include "AndroidCallbacks.h" +#include "AndroidDeviceControllerWrapper.h" + +#include + +using namespace chip; +using namespace chip::Controller; + +static CHIP_ERROR ParseAttributePathList(jobject attributePathList, + std::vector & outAttributePathParamsList); +CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndpointId, ClusterId & outClusterId, + AttributeId & outAttributeId); +static CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector & outEventPathParamsList); +CHIP_ERROR ParseEventPath(jobject eventPath, EndpointId & outEndpointId, ClusterId & outClusterId, EventId & outEventId, + bool & outIsUrgent); +CHIP_ERROR ParseDataVersionFilter(jobject dataVersionFilter, EndpointId & outEndpointId, ClusterId & outClusterId, + DataVersion & outDataVersion); +static CHIP_ERROR ParseDataVersionFilterList(jobject dataVersionFilterList, + std::vector & outDataVersionFilterList); + +CHIP_ERROR subscribe(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, + jobject eventPathList, jobject dataVersionFilterList, jint minInterval, jint maxInterval, + jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + app::ReadClient * readClient = nullptr; + size_t numAttributePaths = 0; + size_t numEventPaths = 0; + size_t numDataVersionFilters = 0; + auto callback = reinterpret_cast(callbackHandle); + DeviceProxy * device = reinterpret_cast(devicePtr); + if (device == nullptr) + { + ChipLogProgress(Controller, "Could not cast device pointer to Device object"); + return CHIP_ERROR_INCORRECT_STATE; + } + + app::ReadPrepareParams params(device->GetSecureSession().Value()); + + uint16_t aImTimeoutMs = static_cast(imTimeoutMs); + params.mMinIntervalFloorSeconds = static_cast(minInterval); + params.mMaxIntervalCeilingSeconds = static_cast(maxInterval); + params.mKeepSubscriptions = (keepSubscriptions != JNI_FALSE); + params.mIsFabricFiltered = (isFabricFiltered != JNI_FALSE); + params.mTimeout = aImTimeoutMs != 0 ? System::Clock::Milliseconds32(aImTimeoutMs) : System::Clock::kZero; + + if (attributePathList != nullptr) + { + jint jNumAttributePaths = 0; + SuccessOrExit(err = JniReferences::GetInstance().GetListSize(attributePathList, jNumAttributePaths)); + numAttributePaths = static_cast(jNumAttributePaths); + } + + if (numAttributePaths > 0) + { + std::unique_ptr attributePaths(new chip::app::AttributePathParams[numAttributePaths]); + for (uint8_t i = 0; i < numAttributePaths; i++) + { + jobject attributePathItem = nullptr; + SuccessOrExit(err = JniReferences::GetInstance().GetListItem(attributePathList, i, attributePathItem)); + + EndpointId endpointId; + ClusterId clusterId; + AttributeId attributeId; + SuccessOrExit(err = ParseAttributePath(attributePathItem, endpointId, clusterId, attributeId)); + attributePaths[i] = chip::app::AttributePathParams(endpointId, clusterId, attributeId); + } + params.mpAttributePathParamsList = attributePaths.get(); + params.mAttributePathParamsListSize = numAttributePaths; + attributePaths.release(); + } + + if (dataVersionFilterList != nullptr) + { + jint jNumDataVersionFilters = 0; + SuccessOrExit(err = JniReferences::GetInstance().GetListSize(dataVersionFilterList, jNumDataVersionFilters)); + numDataVersionFilters = static_cast(jNumDataVersionFilters); + } + + if (numDataVersionFilters > 0) + { + std::unique_ptr dataVersionFilters(new chip::app::DataVersionFilter[numDataVersionFilters]); + for (uint8_t i = 0; i < numDataVersionFilters; i++) + { + jobject dataVersionFilterItem = nullptr; + SuccessOrExit(err = JniReferences::GetInstance().GetListItem(dataVersionFilterList, i, dataVersionFilterItem)); + + EndpointId endpointId; + ClusterId clusterId; + DataVersion dataVersion; + SuccessOrExit(err = ParseDataVersionFilter(dataVersionFilterItem, endpointId, clusterId, dataVersion)); + dataVersionFilters[i] = chip::app::DataVersionFilter(endpointId, clusterId, dataVersion); + } + params.mpDataVersionFilterList = dataVersionFilters.get(); + params.mDataVersionFilterListSize = numDataVersionFilters; + dataVersionFilters.release(); + } + + if (eventMin != nullptr) + { + params.mEventNumber.SetValue(static_cast(JniReferences::GetInstance().LongToPrimitive(eventMin))); + } + + if (eventPathList != nullptr) + { + jint jNumEventPaths = 0; + SuccessOrExit(err = JniReferences::GetInstance().GetListSize(eventPathList, jNumEventPaths)); + numEventPaths = static_cast(jNumEventPaths); + } + + if (numEventPaths > 0) + { + std::unique_ptr eventPaths(new chip::app::EventPathParams[numEventPaths]); + for (uint8_t i = 0; i < numEventPaths; i++) + { + jobject eventPathItem = nullptr; + SuccessOrExit(err = JniReferences::GetInstance().GetListItem(eventPathList, i, eventPathItem)); + + EndpointId endpointId; + ClusterId clusterId; + EventId eventId; + bool isUrgent; + SuccessOrExit(err = ParseEventPath(eventPathItem, endpointId, clusterId, eventId, isUrgent)); + eventPaths[i] = chip::app::EventPathParams(endpointId, clusterId, eventId, isUrgent); + } + + params.mpEventPathParamsList = eventPaths.get(); + params.mEventPathParamsListSize = static_cast(numEventPaths); + eventPaths.release(); + } + + readClient = Platform::New(app::InteractionModelEngine::GetInstance(), device->GetExchangeManager(), + callback->mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Subscribe); + + SuccessOrExit(err = readClient->SendAutoResubscribeRequest(std::move(params))); + callback->mReadClient = readClient; + + return CHIP_NO_ERROR; +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Subscribe Error: %s", err.AsString()); + if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + callback->OnError(err); + if (readClient != nullptr) + { + Platform::Delete(readClient); + } + if (callback != nullptr) + { + Platform::Delete(callback); + } + } + return err; +} + +CHIP_ERROR read(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, + jobject dataVersionFilterList, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + + auto callback = reinterpret_cast(callbackHandle); + std::vector attributePathParamsList; + std::vector eventPathParamsList; + std::vector versionList; + app::ReadClient * readClient = nullptr; + DeviceProxy * device = reinterpret_cast(devicePtr); + if (device == nullptr) + { + ChipLogProgress(Controller, "Could not cast device pointer to Device object"); + return CHIP_ERROR_INCORRECT_STATE; + } + app::ReadPrepareParams params(device->GetSecureSession().Value()); + + SuccessOrExit(err = ParseAttributePathList(attributePathList, attributePathParamsList)); + SuccessOrExit(err = ParseEventPathList(eventPathList, eventPathParamsList)); + SuccessOrExit(err = ParseDataVersionFilterList(dataVersionFilterList, versionList)); + VerifyOrExit(attributePathParamsList.size() != 0 || eventPathParamsList.size() != 0, err = CHIP_ERROR_INVALID_ARGUMENT); + params.mpAttributePathParamsList = attributePathParamsList.data(); + params.mAttributePathParamsListSize = attributePathParamsList.size(); + params.mpEventPathParamsList = eventPathParamsList.data(); + params.mEventPathParamsListSize = eventPathParamsList.size(); + if (versionList.size() != 0) + { + params.mpDataVersionFilterList = versionList.data(); + params.mDataVersionFilterListSize = versionList.size(); + } + + params.mIsFabricFiltered = (isFabricFiltered != JNI_FALSE); + params.mTimeout = imTimeoutMs != 0 ? System::Clock::Milliseconds32(imTimeoutMs) : System::Clock::kZero; + + if (eventMin != nullptr) + { + params.mEventNumber.SetValue(static_cast(JniReferences::GetInstance().LongToPrimitive(eventMin))); + } + + readClient = Platform::New(app::InteractionModelEngine::GetInstance(), device->GetExchangeManager(), + callback->mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Read); + + SuccessOrExit(err = readClient->SendRequest(params)); + callback->mReadClient = readClient; +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Read Error: %s", err.AsString()); + if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + callback->OnError(err); + if (readClient != nullptr) + { + Platform::Delete(readClient); + readClient = nullptr; + } + if (callback != nullptr) + { + Platform::Delete(callback); + callback = nullptr; + } + } + + return err; +} + +// Convert Json to Tlv, and remove the outer structure +CHIP_ERROR ConvertJsonToTlvWithoutStruct(const std::string & json, MutableByteSpan & data) +{ + Platform::ScopedMemoryBufferWithSize buf; + VerifyOrReturnError(buf.Calloc(data.size()), CHIP_ERROR_NO_MEMORY); + MutableByteSpan dataWithStruct(buf.Get(), buf.AllocatedSize()); + ReturnErrorOnFailure(JsonToTlv(json, dataWithStruct)); + TLV::TLVReader tlvReader; + TLV::TLVType outerContainer = TLV::kTLVType_Structure; + tlvReader.Init(dataWithStruct); + ReturnErrorOnFailure(tlvReader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + ReturnErrorOnFailure(tlvReader.EnterContainer(outerContainer)); + ReturnErrorOnFailure(tlvReader.Next()); + + TLV::TLVWriter tlvWrite; + tlvWrite.Init(data); + ReturnErrorOnFailure(tlvWrite.CopyElement(TLV::AnonymousTag(), tlvReader)); + ReturnErrorOnFailure(tlvWrite.Finalize()); + data.reduce_size(tlvWrite.GetLengthWritten()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR PutPreencodedWriteAttribute(app::WriteClient & writeClient, app::ConcreteDataAttributePath & path, const ByteSpan & data) +{ + TLV::TLVReader reader; + reader.Init(data); + ReturnErrorOnFailure(reader.Next()); + return writeClient.PutPreencodedAttribute(path, reader); +} + +CHIP_ERROR write(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, + jint timedRequestTimeoutMs, jint imTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + jint listSize = 0; + auto callback = reinterpret_cast(callbackHandle); + app::WriteClient * writeClient = nullptr; + uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); + + ChipLogDetail(Controller, "IM write() called"); + + DeviceProxy * device = reinterpret_cast(devicePtr); + VerifyOrExit(device != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); + VerifyOrExit(attributeList != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + SuccessOrExit(err = JniReferences::GetInstance().GetListSize(attributeList, listSize)); + + writeClient = Platform::New( + device->GetExchangeManager(), callback->GetChunkedWriteCallback(), + convertedTimedRequestTimeoutMs != 0 ? Optional(convertedTimedRequestTimeoutMs) : Optional::Missing()); + + for (uint8_t i = 0; i < listSize; i++) + { + jobject attributeItem = nullptr; + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getAttributeIdMethod = nullptr; + jmethodID hasDataVersionMethod = nullptr; + jmethodID getDataVersionMethod = nullptr; + jmethodID getTlvByteArrayMethod = nullptr; + jmethodID getJsonStringMethod = nullptr; + jlong endpointIdObj = 0; + jlong clusterIdObj = 0; + jlong attributeIdObj = 0; + jbyteArray tlvBytesObj = nullptr; + bool hasDataVersion = false; + Optional dataVersion = Optional(); + + bool isGroupSession = false; + + SuccessOrExit(err = JniReferences::GetInstance().GetListItem(attributeList, i, attributeItem)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getEndpointId", "(J)J", &getEndpointIdMethod)); + SuccessOrExit(err = + JniReferences::GetInstance().FindMethod(env, attributeItem, "getClusterId", "(J)J", &getClusterIdMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getAttributeId", "(J)J", &getAttributeIdMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, attributeItem, "hasDataVersion", "()Z", &hasDataVersionMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getDataVersion", "()I", &getDataVersionMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getTlvByteArray", "()[B", &getTlvByteArrayMethod)); + + isGroupSession = device->GetSecureSession().Value()->IsGroupSession(); + + if (isGroupSession) + { + endpointIdObj = static_cast(kInvalidEndpointId); + } + else + { + endpointIdObj = env->CallLongMethod(attributeItem, getEndpointIdMethod, static_cast(kInvalidEndpointId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + } + + clusterIdObj = env->CallLongMethod(attributeItem, getClusterIdMethod, static_cast(kInvalidClusterId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + + attributeIdObj = env->CallLongMethod(attributeItem, getAttributeIdMethod, static_cast(kInvalidAttributeId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + + hasDataVersion = static_cast(env->CallBooleanMethod(attributeItem, hasDataVersionMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (hasDataVersion) + { + DataVersion dataVersionVal = static_cast(env->CallIntMethod(attributeItem, getDataVersionMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + dataVersion.SetValue(dataVersionVal); + } + + tlvBytesObj = static_cast(env->CallObjectMethod(attributeItem, getTlvByteArrayMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + app::ConcreteDataAttributePath path(static_cast(endpointIdObj), static_cast(clusterIdObj), + static_cast(attributeIdObj), dataVersion); + if (tlvBytesObj != nullptr) + { + JniByteArray tlvByteArray(env, tlvBytesObj); + SuccessOrExit(err = PutPreencodedWriteAttribute(*writeClient, path, tlvByteArray.byteSpan())); + } + else + { + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getJsonString", "()Ljava/lang/String;", + &getJsonStringMethod)); + jstring jsonJniString = static_cast(env->CallObjectMethod(attributeItem, getJsonStringMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(jsonJniString != nullptr, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + JniUtfString jsonUtfJniString(env, jsonJniString); + std::string jsonString = std::string(jsonUtfJniString.c_str(), static_cast(jsonUtfJniString.size())); + + // Context: Chunk write is supported in sdk, oversized list could be chunked in multiple message. When transforming + // JSON to TLV, we need know the actual size for tlv blob when handling JsonToTlv + // TODO: Implement memory auto-grow to get the actual size needed for tlv blob when transforming tlv to json. + // Workaround: Allocate memory using json string's size, which is large enough to hold the corresponding tlv blob + Platform::ScopedMemoryBufferWithSize tlvBytes; + size_t length = static_cast(jsonUtfJniString.size()); + VerifyOrExit(tlvBytes.Calloc(length), err = CHIP_ERROR_NO_MEMORY); + MutableByteSpan data(tlvBytes.Get(), tlvBytes.AllocatedSize()); + SuccessOrExit(err = ConvertJsonToTlvWithoutStruct(jsonString, data)); + SuccessOrExit(err = PutPreencodedWriteAttribute(*writeClient, path, data)); + } + } + + err = writeClient->SendWriteRequest(device->GetSecureSession().Value(), + imTimeoutMs != 0 ? System::Clock::Milliseconds32(imTimeoutMs) : System::Clock::kZero); + SuccessOrExit(err); + callback->mWriteClient = writeClient; +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Write Error: %s", err.AsString()); + if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + callback->OnError(writeClient, err); + if (writeClient != nullptr) + { + Platform::Delete(writeClient); + writeClient = nullptr; + } + if (callback != nullptr) + { + Platform::Delete(callback); + callback = nullptr; + } + } + return err; +} + +CHIP_ERROR PutPreencodedInvokeRequest(app::CommandSender & commandSender, app::CommandPathParams & path, const ByteSpan & data) +{ + // PrepareCommand does nott create the struct container with kFields and copycontainer below sets the + // kFields container already + ReturnErrorOnFailure(commandSender.PrepareCommand(path, false /* aStartDataStruct */)); + TLV::TLVWriter * writer = commandSender.GetCommandDataIBTLVWriter(); + VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE); + TLV::TLVReader reader; + reader.Init(data); + ReturnErrorOnFailure(reader.Next()); + return writer->CopyContainer(TLV::ContextTag(app::CommandDataIB::Tag::kFields), reader); +} + +CHIP_ERROR invoke(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject invokeElement, + jint timedRequestTimeoutMs, jint imTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + auto callback = reinterpret_cast(callbackHandle); + app::CommandSender * commandSender = nullptr; + uint16_t groupId = 0; + bool isEndpointIdValid = false; + bool isGroupIdValid = false; + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getCommandIdMethod = nullptr; + jmethodID getGroupIdMethod = nullptr; + jmethodID getTlvByteArrayMethod = nullptr; + jmethodID getJsonStringMethod = nullptr; + jmethodID isEndpointIdValidMethod = nullptr; + jmethodID isGroupIdValidMethod = nullptr; + jlong endpointIdObj = 0; + jlong clusterIdObj = 0; + jlong commandIdObj = 0; + jobject groupIdObj = nullptr; + jbyteArray tlvBytesObj = nullptr; + uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); + ChipLogDetail(Controller, "IM invoke() called"); + + DeviceProxy * device = reinterpret_cast(devicePtr); + VerifyOrExit(device != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); + + commandSender = Platform::New(callback, device->GetExchangeManager(), timedRequestTimeoutMs != 0); + + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getEndpointId", "(J)J", &getEndpointIdMethod)); + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getClusterId", "(J)J", &getClusterIdMethod)); + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getCommandId", "(J)J", &getCommandIdMethod)); + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getGroupId", "()Ljava/util/Optional;", + &getGroupIdMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, invokeElement, "isEndpointIdValid", "()Z", &isEndpointIdValidMethod)); + SuccessOrExit(err = + JniReferences::GetInstance().FindMethod(env, invokeElement, "isGroupIdValid", "()Z", &isGroupIdValidMethod)); + SuccessOrExit( + err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getTlvByteArray", "()[B", &getTlvByteArrayMethod)); + + isEndpointIdValid = (env->CallBooleanMethod(invokeElement, isEndpointIdValidMethod) == JNI_TRUE); + isGroupIdValid = (env->CallBooleanMethod(invokeElement, isGroupIdValidMethod) == JNI_TRUE); + + if (isEndpointIdValid) + { + endpointIdObj = env->CallLongMethod(invokeElement, getEndpointIdMethod, static_cast(kInvalidEndpointId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + } + + if (isGroupIdValid) + { + VerifyOrExit(device->GetSecureSession().Value()->IsGroupSession(), err = CHIP_ERROR_INVALID_ARGUMENT); + groupIdObj = env->CallObjectMethod(invokeElement, getGroupIdMethod); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(groupIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + jobject boxedGroupId = nullptr; + + SuccessOrExit(err = JniReferences::GetInstance().GetOptionalValue(groupIdObj, boxedGroupId)); + VerifyOrExit(boxedGroupId != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + groupId = static_cast(JniReferences::GetInstance().IntegerToPrimitive(boxedGroupId)); + } + + clusterIdObj = env->CallLongMethod(invokeElement, getClusterIdMethod, static_cast(kInvalidClusterId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + + commandIdObj = env->CallLongMethod(invokeElement, getCommandIdMethod, static_cast(kInvalidCommandId)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + + tlvBytesObj = static_cast(env->CallObjectMethod(invokeElement, getTlvByteArrayMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + { + uint16_t id = isEndpointIdValid ? static_cast(endpointIdObj) : groupId; + app::CommandPathFlags flag = + isEndpointIdValid ? app::CommandPathFlags::kEndpointIdValid : app::CommandPathFlags::kGroupIdValid; + app::CommandPathParams path(id, static_cast(clusterIdObj), static_cast(commandIdObj), flag); + if (tlvBytesObj != nullptr) + { + JniByteArray tlvBytesObjBytes(env, tlvBytesObj); + SuccessOrExit(err = PutPreencodedInvokeRequest(*commandSender, path, tlvBytesObjBytes.byteSpan())); + } + else + { + SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getJsonString", "()Ljava/lang/String;", + &getJsonStringMethod)); + jstring jsonJniString = static_cast(env->CallObjectMethod(invokeElement, getJsonStringMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(jsonJniString != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + JniUtfString jsonUtfJniString(env, jsonJniString); + // The invoke does not support chunk, kMaxSecureSduLengthBytes should be enough for command json blob + uint8_t tlvBytes[chip::app::kMaxSecureSduLengthBytes] = { 0 }; + MutableByteSpan tlvEncodingLocal{ tlvBytes }; + SuccessOrExit(err = JsonToTlv(std::string(jsonUtfJniString.c_str(), static_cast(jsonUtfJniString.size())), + tlvEncodingLocal)); + SuccessOrExit(err = PutPreencodedInvokeRequest(*commandSender, path, tlvEncodingLocal)); + } + } + SuccessOrExit(err = commandSender->FinishCommand(convertedTimedRequestTimeoutMs != 0 + ? Optional(convertedTimedRequestTimeoutMs) + : Optional::Missing())); + + SuccessOrExit(err = device->GetSecureSession().Value()->IsGroupSession() + ? commandSender->SendGroupCommandRequest(device->GetSecureSession().Value()) + : commandSender->SendCommandRequest(device->GetSecureSession().Value(), + imTimeoutMs != 0 + ? MakeOptional(System::Clock::Milliseconds32(imTimeoutMs)) + : Optional::Missing())); + + callback->mCommandSender = commandSender; +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Invoke Error: %s", err.AsString()); + if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + callback->OnError(nullptr, err); + if (commandSender != nullptr) + { + Platform::Delete(commandSender); + commandSender = nullptr; + } + if (callback != nullptr) + { + Platform::Delete(callback); + callback = nullptr; + } + } + return err; +} + +/** + * Takes objects in attributePathList, converts them to app:AttributePathParams, and appends them to outAttributePathParamsList. + */ +CHIP_ERROR ParseAttributePathList(jobject attributePathList, std::vector & outAttributePathParamsList) +{ + jint listSize; + + if (attributePathList == nullptr) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(attributePathList, listSize)); + + for (uint8_t i = 0; i < listSize; i++) + { + jobject attributePathItem = nullptr; + ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(attributePathList, i, attributePathItem)); + + EndpointId endpointId; + ClusterId clusterId; + AttributeId attributeId; + ReturnErrorOnFailure(ParseAttributePath(attributePathItem, endpointId, clusterId, attributeId)); + outAttributePathParamsList.push_back(app::AttributePathParams(endpointId, clusterId, attributeId)); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndpointId, ClusterId & outClusterId, + AttributeId & outAttributeId) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getAttributeIdMethod = nullptr; + ReturnErrorOnFailure( + JniReferences::GetInstance().FindMethod(env, attributePath, "getEndpointId", "(J)J", &getEndpointIdMethod)); + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, attributePath, "getClusterId", "(J)J", &getClusterIdMethod)); + ReturnErrorOnFailure( + JniReferences::GetInstance().FindMethod(env, attributePath, "getAttributeId", "(J)J", &getAttributeIdMethod)); + + jlong endpointIdObj = env->CallLongMethod(attributePath, getEndpointIdMethod, static_cast(kInvalidEndpointId)); + jlong clusterIdObj = env->CallLongMethod(attributePath, getClusterIdMethod, static_cast(kInvalidClusterId)); + jlong attributeIdObj = env->CallLongMethod(attributePath, getAttributeIdMethod, static_cast(kInvalidAttributeId)); + + outEndpointId = static_cast(endpointIdObj); + outClusterId = static_cast(clusterIdObj); + outAttributeId = static_cast(attributeIdObj); + + return CHIP_NO_ERROR; +} + +/** + * Takes objects in eventPathList, converts them to app:EventPathParams, and appends them to outEventPathParamsList. + */ +CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector & outEventPathParamsList) +{ + jint listSize; + + if (eventPathList == nullptr) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(eventPathList, listSize)); + + for (uint8_t i = 0; i < listSize; i++) + { + jobject eventPathItem = nullptr; + ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(eventPathList, i, eventPathItem)); + + EndpointId endpointId; + ClusterId clusterId; + EventId eventId; + bool isUrgent; + ReturnErrorOnFailure(ParseEventPath(eventPathItem, endpointId, clusterId, eventId, isUrgent)); + outEventPathParamsList.push_back(app::EventPathParams(endpointId, clusterId, eventId, isUrgent)); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ParseEventPath(jobject eventPath, EndpointId & outEndpointId, ClusterId & outClusterId, EventId & outEventId, + bool & outIsUrgent) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getEventIdMethod = nullptr; + jmethodID isUrgentMethod = nullptr; + + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "getEndpointId", "(J)J", &getEndpointIdMethod)); + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "getClusterId", "(J)J", &getClusterIdMethod)); + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "getEventId", "(J)J", &getEventIdMethod)); + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "isUrgent", "()Z", &isUrgentMethod)); + + jlong endpointIdObj = env->CallLongMethod(eventPath, getEndpointIdMethod, static_cast(kInvalidEndpointId)); + jlong clusterIdObj = env->CallLongMethod(eventPath, getClusterIdMethod, static_cast(kInvalidClusterId)); + jlong eventIdObj = env->CallLongMethod(eventPath, getEventIdMethod, static_cast(kInvalidEventId)); + jboolean isUrgent = env->CallBooleanMethod(eventPath, isUrgentMethod); + + outEndpointId = static_cast(endpointIdObj); + outClusterId = static_cast(clusterIdObj); + outEventId = static_cast(eventIdObj); + outIsUrgent = (isUrgent == JNI_TRUE); + + return CHIP_NO_ERROR; +} + +/** + * Takes objects in dataVersionFilterList, converts them to app:DataVersionFilter, and appends them to outDataVersionFilterList. + */ +CHIP_ERROR ParseDataVersionFilterList(jobject dataVersionFilterList, std::vector & outDataVersionFilterList) +{ + jint listSize; + + if (dataVersionFilterList == nullptr) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(dataVersionFilterList, listSize)); + + for (uint8_t i = 0; i < listSize; i++) + { + jobject dataVersionFilterItem = nullptr; + ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(dataVersionFilterList, i, dataVersionFilterItem)); + + EndpointId endpointId; + ClusterId clusterId; + DataVersion dataVersion; + ReturnErrorOnFailure(ParseDataVersionFilter(dataVersionFilterItem, endpointId, clusterId, dataVersion)); + outDataVersionFilterList.push_back(app::DataVersionFilter(endpointId, clusterId, dataVersion)); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ParseDataVersionFilter(jobject versionFilter, EndpointId & outEndpointId, ClusterId & outClusterId, + DataVersion & outDataVersion) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getDataVersionMethod = nullptr; + + ReturnErrorOnFailure( + JniReferences::GetInstance().FindMethod(env, versionFilter, "getEndpointId", "(J)J", &getEndpointIdMethod)); + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, versionFilter, "getClusterId", "(J)J", &getClusterIdMethod)); + ReturnErrorOnFailure( + JniReferences::GetInstance().FindMethod(env, versionFilter, "getDataVersion", "()J", &getDataVersionMethod)); + + jlong endpointIdObj = env->CallLongMethod(versionFilter, getEndpointIdMethod, static_cast(kInvalidEndpointId)); + outEndpointId = static_cast(endpointIdObj); + jlong clusterIdObj = env->CallLongMethod(versionFilter, getClusterIdMethod, static_cast(kInvalidClusterId)); + outClusterId = static_cast(clusterIdObj); + + outDataVersion = static_cast(env->CallLongMethod(versionFilter, getDataVersionMethod)); + return CHIP_NO_ERROR; +} diff --git a/src/controller/java/AndroidInteractionClient.h b/src/controller/java/AndroidInteractionClient.h new file mode 100644 index 00000000000000..095061f0c98172 --- /dev/null +++ b/src/controller/java/AndroidInteractionClient.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#pragma once + +#include + +CHIP_ERROR subscribe(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, + jobject eventPathList, jobject dataVersionFilterList, jint minInterval, jint maxInterval, + jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin); +CHIP_ERROR read(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, + jobject dataVersionFilterList, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin); +CHIP_ERROR write(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, + jint timedRequestTimeoutMs, jint imTimeoutMs); +CHIP_ERROR invoke(JNIEnv * env, jlong handle, jlong callbackHandle, jlong devicePtr, jobject invokeElement, + jint timedRequestTimeoutMs, jint imTimeoutMs); diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 0e5beda7115ae6..b2ec93cb67f160 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -53,6 +53,8 @@ shared_library("jni") { "AndroidCurrentFabricRemover.h", "AndroidDeviceControllerWrapper.cpp", "AndroidDeviceControllerWrapper.h", + "AndroidInteractionClient.cpp", + "AndroidInteractionClient.h", "AndroidOperationalCredentialsIssuer.cpp", "AndroidOperationalCredentialsIssuer.h", "AttestationTrustStoreBridge.cpp", @@ -64,6 +66,8 @@ shared_library("jni") { "DeviceAttestationDelegateBridge.cpp", "DeviceAttestationDelegateBridge.h", "GroupDeviceProxy.h", + "MatterCallbacks-JNI.cpp", + "MatterController-JNI.cpp", ] deps = [ @@ -371,12 +375,20 @@ kotlin_library("kotlin_matter_controller") { "src/matter/controller/CompletionListenerAdapter.kt", "src/matter/controller/ControllerParams.kt", "src/matter/controller/InteractionClient.kt", + "src/matter/controller/InvokeCallback.kt", + "src/matter/controller/InvokeCallbackJni.kt", "src/matter/controller/MatterController.kt", "src/matter/controller/MatterControllerException.kt", "src/matter/controller/MatterControllerImpl.kt", "src/matter/controller/Messages.kt", "src/matter/controller/OperationalKeyConfig.kt", + "src/matter/controller/ReportCallback.kt", + "src/matter/controller/ReportCallbackJni.kt", + "src/matter/controller/ResubscriptionAttemptCallback.kt", + "src/matter/controller/SubscriptionEstablishedCallback.kt", "src/matter/controller/SubscriptionStates.kt", + "src/matter/controller/WriteAttributesCallback.kt", + "src/matter/controller/WriteAttributesCallbackJni.kt", "src/matter/controller/model/Paths.kt", "src/matter/controller/model/States.kt", ] diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 85c37b87ae6691..206103ca82a311 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -25,6 +25,7 @@ #include "AndroidCommissioningWindowOpener.h" #include "AndroidCurrentFabricRemover.h" #include "AndroidDeviceControllerWrapper.h" +#include "AndroidInteractionClient.h" #include #include #include @@ -85,19 +86,6 @@ static void * IOThreadMain(void * arg); static CHIP_ERROR StopIOThread(); static CHIP_ERROR N2J_PaseVerifierParams(JNIEnv * env, jlong setupPincode, jbyteArray pakeVerifier, jobject & outParams); static CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jint interfaceIndex, jobject & outLocation); -static CHIP_ERROR GetChipPathIdValue(jobject chipPathId, uint32_t wildcardValue, uint32_t & outValue); -static CHIP_ERROR ParseAttributePathList(jobject attributePathList, - std::vector & outAttributePathParamsList); -CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndpointId, ClusterId & outClusterId, - AttributeId & outAttributeId); -static CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector & outEventPathParamsList); -CHIP_ERROR ParseEventPath(jobject eventPath, EndpointId & outEndpointId, ClusterId & outClusterId, EventId & outEventId, - bool & outIsUrgent); -CHIP_ERROR ParseDataVersionFilter(jobject dataVersionFilter, EndpointId & outEndpointId, ClusterId & outClusterId, - DataVersion & outDataVersion); -static CHIP_ERROR ParseDataVersionFilterList(jobject dataVersionFilterList, - std::vector & outDataVersionFilterList); -static CHIP_ERROR IsWildcardChipPathId(jobject chipPathId, bool & isWildcard); namespace { JavaVM * sJVM = nullptr; @@ -2255,139 +2243,11 @@ JNI_METHOD(void, subscribe) jobject dataVersionFilterList, jint minInterval, jint maxInterval, jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin) { - chip::DeviceLayer::StackLock lock; - CHIP_ERROR err = CHIP_NO_ERROR; - app::ReadClient * readClient = nullptr; - size_t numAttributePaths = 0; - size_t numEventPaths = 0; - size_t numDataVersionFilters = 0; - auto callback = reinterpret_cast(callbackHandle); - DeviceProxy * device = reinterpret_cast(devicePtr); - if (device == nullptr) - { - ChipLogProgress(Controller, "Could not cast device pointer to Device object"); - JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, CHIP_ERROR_INCORRECT_STATE); - return; - } - app::ReadPrepareParams params(device->GetSecureSession().Value()); - - uint16_t aImTimeoutMs = static_cast(imTimeoutMs); - params.mMinIntervalFloorSeconds = static_cast(minInterval); - params.mMaxIntervalCeilingSeconds = static_cast(maxInterval); - params.mKeepSubscriptions = (keepSubscriptions != JNI_FALSE); - params.mIsFabricFiltered = (isFabricFiltered != JNI_FALSE); - params.mTimeout = aImTimeoutMs != 0 ? System::Clock::Milliseconds32(aImTimeoutMs) : System::Clock::kZero; - - if (attributePathList != nullptr) - { - jint jNumAttributePaths = 0; - SuccessOrExit(err = JniReferences::GetInstance().GetListSize(attributePathList, jNumAttributePaths)); - numAttributePaths = static_cast(jNumAttributePaths); - } - - if (numAttributePaths > 0) - { - std::unique_ptr attributePaths(new chip::app::AttributePathParams[numAttributePaths]); - for (uint8_t i = 0; i < numAttributePaths; i++) - { - jobject attributePathItem = nullptr; - SuccessOrExit(err = JniReferences::GetInstance().GetListItem(attributePathList, i, attributePathItem)); - - EndpointId endpointId; - ClusterId clusterId; - AttributeId attributeId; - SuccessOrExit(err = ParseAttributePath(attributePathItem, endpointId, clusterId, attributeId)); - attributePaths[i] = chip::app::AttributePathParams(endpointId, clusterId, attributeId); - } - params.mpAttributePathParamsList = attributePaths.get(); - params.mAttributePathParamsListSize = numAttributePaths; - attributePaths.release(); - } - - if (dataVersionFilterList != nullptr) - { - jint jNumDataVersionFilters = 0; - SuccessOrExit(err = JniReferences::GetInstance().GetListSize(dataVersionFilterList, jNumDataVersionFilters)); - numDataVersionFilters = static_cast(jNumDataVersionFilters); - } - - if (numDataVersionFilters > 0) - { - std::unique_ptr dataVersionFilters(new chip::app::DataVersionFilter[numDataVersionFilters]); - for (uint8_t i = 0; i < numDataVersionFilters; i++) - { - jobject dataVersionFilterItem = nullptr; - SuccessOrExit(err = JniReferences::GetInstance().GetListItem(dataVersionFilterList, i, dataVersionFilterItem)); - - EndpointId endpointId; - ClusterId clusterId; - DataVersion dataVersion; - SuccessOrExit(err = ParseDataVersionFilter(dataVersionFilterItem, endpointId, clusterId, dataVersion)); - dataVersionFilters[i] = chip::app::DataVersionFilter(endpointId, clusterId, dataVersion); - } - params.mpDataVersionFilterList = dataVersionFilters.get(); - params.mDataVersionFilterListSize = numDataVersionFilters; - dataVersionFilters.release(); - } - - if (eventMin != nullptr) - { - params.mEventNumber.SetValue(static_cast(JniReferences::GetInstance().LongToPrimitive(eventMin))); - } - - if (eventPathList != nullptr) - { - jint jNumEventPaths = 0; - SuccessOrExit(err = JniReferences::GetInstance().GetListSize(eventPathList, jNumEventPaths)); - numEventPaths = static_cast(jNumEventPaths); - } - - if (numEventPaths > 0) - { - std::unique_ptr eventPaths(new chip::app::EventPathParams[numEventPaths]); - for (uint8_t i = 0; i < numEventPaths; i++) - { - jobject eventPathItem = nullptr; - SuccessOrExit(err = JniReferences::GetInstance().GetListItem(eventPathList, i, eventPathItem)); - - EndpointId endpointId; - ClusterId clusterId; - EventId eventId; - bool isUrgent; - SuccessOrExit(err = ParseEventPath(eventPathItem, endpointId, clusterId, eventId, isUrgent)); - eventPaths[i] = chip::app::EventPathParams(endpointId, clusterId, eventId, isUrgent); - } - - params.mpEventPathParamsList = eventPaths.get(); - params.mEventPathParamsListSize = static_cast(numEventPaths); - eventPaths.release(); - } - - readClient = Platform::New(app::InteractionModelEngine::GetInstance(), device->GetExchangeManager(), - callback->mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Subscribe); - - SuccessOrExit(err = readClient->SendAutoResubscribeRequest(std::move(params))); - callback->mReadClient = readClient; - -exit: + CHIP_ERROR err = subscribe(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, dataVersionFilterList, + minInterval, maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, eventMin); if (err != CHIP_NO_ERROR) { - ChipLogError(Controller, "JNI IM Subscribe Error: %s", err.AsString()); - if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - callback->OnError(err); - if (readClient != nullptr) - { - Platform::Delete(readClient); - } - if (callback != nullptr) - { - Platform::Delete(callback); - } + ChipLogError(Controller, "JNI IM Subscribe Error: %" CHIP_ERROR_FORMAT, err.Format()); } } @@ -2395,659 +2255,34 @@ JNI_METHOD(void, read) (JNIEnv * env, jclass clz, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, jobject dataVersionFilterList, jboolean isFabricFiltered, jint imTimeoutMs, jobject eventMin) { - chip::DeviceLayer::StackLock lock; - CHIP_ERROR err = CHIP_NO_ERROR; - - auto callback = reinterpret_cast(callbackHandle); - std::vector attributePathParamsList; - std::vector eventPathParamsList; - std::vector versionList; - app::ReadClient * readClient = nullptr; - DeviceProxy * device = reinterpret_cast(devicePtr); - if (device == nullptr) - { - ChipLogProgress(Controller, "Could not cast device pointer to Device object"); - JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, CHIP_ERROR_INCORRECT_STATE); - return; - } - app::ReadPrepareParams params(device->GetSecureSession().Value()); - - SuccessOrExit(err = ParseAttributePathList(attributePathList, attributePathParamsList)); - SuccessOrExit(err = ParseEventPathList(eventPathList, eventPathParamsList)); - SuccessOrExit(err = ParseDataVersionFilterList(dataVersionFilterList, versionList)); - VerifyOrExit(attributePathParamsList.size() != 0 || eventPathParamsList.size() != 0, err = CHIP_ERROR_INVALID_ARGUMENT); - params.mpAttributePathParamsList = attributePathParamsList.data(); - params.mAttributePathParamsListSize = attributePathParamsList.size(); - params.mpEventPathParamsList = eventPathParamsList.data(); - params.mEventPathParamsListSize = eventPathParamsList.size(); - if (versionList.size() != 0) - { - params.mpDataVersionFilterList = versionList.data(); - params.mDataVersionFilterListSize = versionList.size(); - } - - params.mIsFabricFiltered = (isFabricFiltered != JNI_FALSE); - params.mTimeout = imTimeoutMs != 0 ? System::Clock::Milliseconds32(imTimeoutMs) : System::Clock::kZero; - - if (eventMin != nullptr) - { - params.mEventNumber.SetValue(static_cast(JniReferences::GetInstance().LongToPrimitive(eventMin))); - } - - readClient = Platform::New(app::InteractionModelEngine::GetInstance(), device->GetExchangeManager(), - callback->mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); - - SuccessOrExit(err = readClient->SendRequest(params)); - callback->mReadClient = readClient; - -exit: + CHIP_ERROR err = read(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, dataVersionFilterList, + isFabricFiltered, imTimeoutMs, eventMin); if (err != CHIP_NO_ERROR) { - ChipLogError(Controller, "JNI IM Read Error: %s", err.AsString()); - if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - callback->OnError(err); - if (readClient != nullptr) - { - Platform::Delete(readClient); - } - if (callback != nullptr) - { - Platform::Delete(callback); - } + ChipLogError(Controller, "JNI IM Read Error: %" CHIP_ERROR_FORMAT, err.Format()); } } -// Convert Json to Tlv, and remove the outer structure -CHIP_ERROR ConvertJsonToTlvWithoutStruct(const std::string & json, MutableByteSpan & data) -{ - Platform::ScopedMemoryBufferWithSize buf; - VerifyOrReturnError(buf.Calloc(data.size()), CHIP_ERROR_NO_MEMORY); - MutableByteSpan dataWithStruct(buf.Get(), buf.AllocatedSize()); - ReturnErrorOnFailure(JsonToTlv(json, dataWithStruct)); - TLV::TLVReader tlvReader; - TLV::TLVType outerContainer = TLV::kTLVType_Structure; - tlvReader.Init(dataWithStruct); - ReturnErrorOnFailure(tlvReader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); - ReturnErrorOnFailure(tlvReader.EnterContainer(outerContainer)); - ReturnErrorOnFailure(tlvReader.Next()); - - TLV::TLVWriter tlvWrite; - tlvWrite.Init(data); - ReturnErrorOnFailure(tlvWrite.CopyElement(TLV::AnonymousTag(), tlvReader)); - ReturnErrorOnFailure(tlvWrite.Finalize()); - data.reduce_size(tlvWrite.GetLengthWritten()); - return CHIP_NO_ERROR; -} - -CHIP_ERROR PutPreencodedWriteAttribute(app::WriteClient & writeClient, app::ConcreteDataAttributePath & path, const ByteSpan & data) -{ - TLV::TLVReader reader; - reader.Init(data); - ReturnErrorOnFailure(reader.Next()); - return writeClient.PutPreencodedAttribute(path, reader); -} - JNI_METHOD(void, write) (JNIEnv * env, jclass clz, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, jint timedRequestTimeoutMs, jint imTimeoutMs) { - chip::DeviceLayer::StackLock lock; - CHIP_ERROR err = CHIP_NO_ERROR; - jint listSize = 0; - auto callback = reinterpret_cast(callbackHandle); - app::WriteClient * writeClient = nullptr; - uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); - - ChipLogDetail(Controller, "IM write() called"); - - DeviceProxy * device = reinterpret_cast(devicePtr); - VerifyOrExit(device != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); - VerifyOrExit(attributeList != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - SuccessOrExit(err = JniReferences::GetInstance().GetListSize(attributeList, listSize)); - - writeClient = Platform::New( - device->GetExchangeManager(), callback->GetChunkedWriteCallback(), - convertedTimedRequestTimeoutMs != 0 ? Optional(convertedTimedRequestTimeoutMs) : Optional::Missing()); - - for (uint8_t i = 0; i < listSize; i++) - { - jobject attributeItem = nullptr; - uint32_t endpointId = 0; - uint32_t clusterId = 0; - uint32_t attributeId = 0; - jmethodID getEndpointIdMethod = nullptr; - jmethodID getClusterIdMethod = nullptr; - jmethodID getAttributeIdMethod = nullptr; - jmethodID hasDataVersionMethod = nullptr; - jmethodID getDataVersionMethod = nullptr; - jmethodID getTlvByteArrayMethod = nullptr; - jmethodID getJsonStringMethod = nullptr; - jobject endpointIdObj = nullptr; - jobject clusterIdObj = nullptr; - jobject attributeIdObj = nullptr; - jbyteArray tlvBytesObj = nullptr; - bool hasDataVersion = false; - Optional dataVersion = Optional(); - - bool isGroupSession = false; - - SuccessOrExit(err = JniReferences::GetInstance().GetListItem(attributeList, i, attributeItem)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod( - env, attributeItem, "getEndpointId", "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod( - env, attributeItem, "getClusterId", "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getAttributeId", - "()Lchip/devicecontroller/model/ChipPathId;", - &getAttributeIdMethod)); - SuccessOrExit( - err = JniReferences::GetInstance().FindMethod(env, attributeItem, "hasDataVersion", "()Z", &hasDataVersionMethod)); - SuccessOrExit( - err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getDataVersion", "()I", &getDataVersionMethod)); - SuccessOrExit( - err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getTlvByteArray", "()[B", &getTlvByteArrayMethod)); - - isGroupSession = device->GetSecureSession().Value()->IsGroupSession(); - - if (isGroupSession) - { - endpointId = kInvalidEndpointId; - } - else - { - endpointIdObj = env->CallObjectMethod(attributeItem, getEndpointIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(endpointIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - SuccessOrExit(err = GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); - } - - clusterIdObj = env->CallObjectMethod(attributeItem, getClusterIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(clusterIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - attributeIdObj = env->CallObjectMethod(attributeItem, getAttributeIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(attributeIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - SuccessOrExit(err = GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); - SuccessOrExit(err = GetChipPathIdValue(attributeIdObj, kInvalidAttributeId, attributeId)); - - hasDataVersion = static_cast(env->CallBooleanMethod(attributeItem, hasDataVersionMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - if (hasDataVersion) - { - DataVersion dataVersionVal = static_cast(env->CallIntMethod(attributeItem, getDataVersionMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - dataVersion.SetValue(dataVersionVal); - } - - tlvBytesObj = static_cast(env->CallObjectMethod(attributeItem, getTlvByteArrayMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - app::ConcreteDataAttributePath path(static_cast(endpointId), static_cast(clusterId), - static_cast(attributeId), dataVersion); - if (tlvBytesObj != nullptr) - { - JniByteArray tlvByteArray(env, tlvBytesObj); - SuccessOrExit(err = PutPreencodedWriteAttribute(*writeClient, path, tlvByteArray.byteSpan())); - } - else - { - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, attributeItem, "getJsonString", "()Ljava/lang/String;", - &getJsonStringMethod)); - jstring jsonJniString = static_cast(env->CallObjectMethod(attributeItem, getJsonStringMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(jsonJniString != nullptr, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - JniUtfString jsonUtfJniString(env, jsonJniString); - std::string jsonString = std::string(jsonUtfJniString.c_str(), static_cast(jsonUtfJniString.size())); - - // Context: Chunk write is supported in sdk, oversized list could be chunked in multiple message. When transforming - // JSON to TLV, we need know the actual size for tlv blob when handling JsonToTlv - // TODO: Implement memory auto-grow to get the actual size needed for tlv blob when transforming tlv to json. - // Workaround: Allocate memory using json string's size, which is large enough to hold the corresponding tlv blob - Platform::ScopedMemoryBufferWithSize tlvBytes; - size_t length = static_cast(jsonUtfJniString.size()); - VerifyOrExit(tlvBytes.Calloc(length), err = CHIP_ERROR_NO_MEMORY); - MutableByteSpan data(tlvBytes.Get(), tlvBytes.AllocatedSize()); - SuccessOrExit(err = ConvertJsonToTlvWithoutStruct(jsonString, data)); - SuccessOrExit(err = PutPreencodedWriteAttribute(*writeClient, path, data)); - } - } - - err = writeClient->SendWriteRequest(device->GetSecureSession().Value(), - imTimeoutMs != 0 ? System::Clock::Milliseconds32(imTimeoutMs) : System::Clock::kZero); - SuccessOrExit(err); - callback->mWriteClient = writeClient; - -exit: + CHIP_ERROR err = write(env, handle, callbackHandle, devicePtr, attributeList, timedRequestTimeoutMs, imTimeoutMs); if (err != CHIP_NO_ERROR) { - ChipLogError(Controller, "JNI IM Write Error: %s", err.AsString()); - if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - callback->OnError(writeClient, err); - if (writeClient != nullptr) - { - Platform::Delete(writeClient); - } - if (callback != nullptr) - { - Platform::Delete(callback); - } + ChipLogError(Controller, "JNI IM Write Error: %" CHIP_ERROR_FORMAT, err.Format()); } } -CHIP_ERROR PutPreencodedInvokeRequest(app::CommandSender & commandSender, app::CommandPathParams & path, const ByteSpan & data) -{ - // PrepareCommand does nott create the struct container with kFields and copycontainer below sets the - // kFields container already - ReturnErrorOnFailure(commandSender.PrepareCommand(path, false /* aStartDataStruct */)); - TLV::TLVWriter * writer = commandSender.GetCommandDataIBTLVWriter(); - VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE); - TLV::TLVReader reader; - reader.Init(data); - ReturnErrorOnFailure(reader.Next()); - return writer->CopyContainer(TLV::ContextTag(app::CommandDataIB::Tag::kFields), reader); -} - JNI_METHOD(void, invoke) (JNIEnv * env, jclass clz, jlong handle, jlong callbackHandle, jlong devicePtr, jobject invokeElement, jint timedRequestTimeoutMs, jint imTimeoutMs) { - chip::DeviceLayer::StackLock lock; - CHIP_ERROR err = CHIP_NO_ERROR; - auto callback = reinterpret_cast(callbackHandle); - app::CommandSender * commandSender = nullptr; - uint32_t endpointId = 0; - uint32_t clusterId = 0; - uint32_t commandId = 0; - uint16_t groupId = 0; - bool isEndpointIdValid = false; - bool isGroupIdValid = false; - jmethodID getEndpointIdMethod = nullptr; - jmethodID getClusterIdMethod = nullptr; - jmethodID getCommandIdMethod = nullptr; - jmethodID getGroupIdMethod = nullptr; - jmethodID getTlvByteArrayMethod = nullptr; - jmethodID getJsonStringMethod = nullptr; - jmethodID isEndpointIdValidMethod = nullptr; - jmethodID isGroupIdValidMethod = nullptr; - jobject endpointIdObj = nullptr; - jobject clusterIdObj = nullptr; - jobject commandIdObj = nullptr; - jobject groupIdObj = nullptr; - jbyteArray tlvBytesObj = nullptr; - uint16_t convertedTimedRequestTimeoutMs = static_cast(timedRequestTimeoutMs); - ChipLogDetail(Controller, "IM invoke() called"); - - DeviceProxy * device = reinterpret_cast(devicePtr); - VerifyOrExit(device != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); - - commandSender = Platform::New(callback, device->GetExchangeManager(), timedRequestTimeoutMs != 0); - - SuccessOrExit(err = JniReferences::GetInstance().FindMethod( - env, invokeElement, "getEndpointId", "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getClusterId", - "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getCommandId", - "()Lchip/devicecontroller/model/ChipPathId;", &getCommandIdMethod)); - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getGroupId", "()Ljava/util/Optional;", - &getGroupIdMethod)); - SuccessOrExit( - err = JniReferences::GetInstance().FindMethod(env, invokeElement, "isEndpointIdValid", "()Z", &isEndpointIdValidMethod)); - SuccessOrExit(err = - JniReferences::GetInstance().FindMethod(env, invokeElement, "isGroupIdValid", "()Z", &isGroupIdValidMethod)); - SuccessOrExit( - err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getTlvByteArray", "()[B", &getTlvByteArrayMethod)); - - isEndpointIdValid = (env->CallBooleanMethod(invokeElement, isEndpointIdValidMethod) == JNI_TRUE); - isGroupIdValid = (env->CallBooleanMethod(invokeElement, isGroupIdValidMethod) == JNI_TRUE); - - if (isEndpointIdValid) - { - endpointIdObj = env->CallObjectMethod(invokeElement, getEndpointIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(endpointIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - SuccessOrExit(err = GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); - } - - if (isGroupIdValid) - { - VerifyOrExit(device->GetSecureSession().Value()->IsGroupSession(), err = CHIP_ERROR_INVALID_ARGUMENT); - groupIdObj = env->CallObjectMethod(invokeElement, getGroupIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(groupIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - jobject boxedGroupId = nullptr; - - SuccessOrExit(err = JniReferences::GetInstance().GetOptionalValue(groupIdObj, boxedGroupId)); - VerifyOrExit(boxedGroupId != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - groupId = static_cast(JniReferences::GetInstance().IntegerToPrimitive(boxedGroupId)); - } - - clusterIdObj = env->CallObjectMethod(invokeElement, getClusterIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(clusterIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - commandIdObj = env->CallObjectMethod(invokeElement, getCommandIdMethod); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(commandIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - - SuccessOrExit(err = GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); - SuccessOrExit(err = GetChipPathIdValue(commandIdObj, kInvalidCommandId, commandId)); - - tlvBytesObj = static_cast(env->CallObjectMethod(invokeElement, getTlvByteArrayMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - { - uint16_t id = isEndpointIdValid ? static_cast(endpointId) : groupId; - app::CommandPathFlags flag = - isEndpointIdValid ? app::CommandPathFlags::kEndpointIdValid : app::CommandPathFlags::kGroupIdValid; - app::CommandPathParams path(id, static_cast(clusterId), static_cast(commandId), flag); - if (tlvBytesObj != nullptr) - { - JniByteArray tlvBytesObjBytes(env, tlvBytesObj); - SuccessOrExit(err = PutPreencodedInvokeRequest(*commandSender, path, tlvBytesObjBytes.byteSpan())); - } - else - { - SuccessOrExit(err = JniReferences::GetInstance().FindMethod(env, invokeElement, "getJsonString", "()Ljava/lang/String;", - &getJsonStringMethod)); - jstring jsonJniString = static_cast(env->CallObjectMethod(invokeElement, getJsonStringMethod)); - VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrExit(jsonJniString != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); - JniUtfString jsonUtfJniString(env, jsonJniString); - // The invoke does not support chunk, kMaxSecureSduLengthBytes should be enough for command json blob - uint8_t tlvBytes[chip::app::kMaxSecureSduLengthBytes] = { 0 }; - MutableByteSpan tlvEncodingLocal{ tlvBytes }; - SuccessOrExit(err = JsonToTlv(std::string(jsonUtfJniString.c_str(), static_cast(jsonUtfJniString.size())), - tlvEncodingLocal)); - SuccessOrExit(err = PutPreencodedInvokeRequest(*commandSender, path, tlvEncodingLocal)); - } - } - SuccessOrExit(err = commandSender->FinishCommand(convertedTimedRequestTimeoutMs != 0 - ? Optional(convertedTimedRequestTimeoutMs) - : Optional::Missing())); - - SuccessOrExit(err = device->GetSecureSession().Value()->IsGroupSession() - ? commandSender->SendGroupCommandRequest(device->GetSecureSession().Value()) - : commandSender->SendCommandRequest(device->GetSecureSession().Value(), - imTimeoutMs != 0 - ? MakeOptional(System::Clock::Milliseconds32(imTimeoutMs)) - : Optional::Missing())); - - callback->mCommandSender = commandSender; - -exit: + CHIP_ERROR err = invoke(env, handle, callbackHandle, devicePtr, invokeElement, timedRequestTimeoutMs, imTimeoutMs); if (err != CHIP_NO_ERROR) { - ChipLogError(Controller, "JNI IM Invoke Error: %s", err.AsString()); - if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - callback->OnError(nullptr, err); - if (commandSender != nullptr) - { - Platform::Delete(commandSender); - } - if (callback != nullptr) - { - Platform::Delete(callback); - } - } -} - -/** - * Takes objects in attributePathList, converts them to app:AttributePathParams, and appends them to outAttributePathParamsList. - */ -CHIP_ERROR ParseAttributePathList(jobject attributePathList, std::vector & outAttributePathParamsList) -{ - jint listSize; - - if (attributePathList == nullptr) - { - return CHIP_NO_ERROR; - } - - ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(attributePathList, listSize)); - - for (uint8_t i = 0; i < listSize; i++) - { - jobject attributePathItem = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(attributePathList, i, attributePathItem)); - - EndpointId endpointId; - ClusterId clusterId; - AttributeId attributeId; - ReturnErrorOnFailure(ParseAttributePath(attributePathItem, endpointId, clusterId, attributeId)); - outAttributePathParamsList.push_back(app::AttributePathParams(endpointId, clusterId, attributeId)); - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndpointId, ClusterId & outClusterId, - AttributeId & outAttributeId) -{ - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - jmethodID getEndpointIdMethod = nullptr; - jmethodID getClusterIdMethod = nullptr; - jmethodID getAttributeIdMethod = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, attributePath, "getEndpointId", "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, attributePath, "getClusterId", "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, attributePath, "getAttributeId", "()Lchip/devicecontroller/model/ChipPathId;", &getAttributeIdMethod)); - - jobject endpointIdObj = env->CallObjectMethod(attributePath, getEndpointIdMethod); - VerifyOrReturnError(endpointIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - jobject clusterIdObj = env->CallObjectMethod(attributePath, getClusterIdMethod); - VerifyOrReturnError(clusterIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - jobject attributeIdObj = env->CallObjectMethod(attributePath, getAttributeIdMethod); - VerifyOrReturnError(attributeIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - - uint32_t endpointId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); - uint32_t clusterId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); - uint32_t attributeId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(attributeIdObj, kInvalidAttributeId, attributeId)); - - outEndpointId = static_cast(endpointId); - outClusterId = static_cast(clusterId); - outAttributeId = static_cast(attributeId); - - return CHIP_NO_ERROR; -} - -/** - * Takes objects in eventPathList, converts them to app:EventPathParams, and appends them to outEventPathParamsList. - */ -CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector & outEventPathParamsList) -{ - jint listSize; - - if (eventPathList == nullptr) - { - return CHIP_NO_ERROR; - } - - ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(eventPathList, listSize)); - - for (uint8_t i = 0; i < listSize; i++) - { - jobject eventPathItem = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(eventPathList, i, eventPathItem)); - - EndpointId endpointId; - ClusterId clusterId; - EventId eventId; - bool isUrgent; - ReturnErrorOnFailure(ParseEventPath(eventPathItem, endpointId, clusterId, eventId, isUrgent)); - outEventPathParamsList.push_back(app::EventPathParams(endpointId, clusterId, eventId, isUrgent)); - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR ParseEventPath(jobject eventPath, EndpointId & outEndpointId, ClusterId & outClusterId, EventId & outEventId, - bool & outIsUrgent) -{ - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - jmethodID getEndpointIdMethod = nullptr; - jmethodID getClusterIdMethod = nullptr; - jmethodID getEventIdMethod = nullptr; - jmethodID isUrgentMethod = nullptr; - - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, eventPath, "getEndpointId", "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, eventPath, "getClusterId", "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "getEventId", - "()Lchip/devicecontroller/model/ChipPathId;", &getEventIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, eventPath, "isUrgent", "()Z", &isUrgentMethod)); - - jobject endpointIdObj = env->CallObjectMethod(eventPath, getEndpointIdMethod); - VerifyOrReturnError(endpointIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - jobject clusterIdObj = env->CallObjectMethod(eventPath, getClusterIdMethod); - VerifyOrReturnError(clusterIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - jobject eventIdObj = env->CallObjectMethod(eventPath, getEventIdMethod); - VerifyOrReturnError(eventIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - jboolean isUrgent = env->CallBooleanMethod(eventPath, isUrgentMethod); - - uint32_t endpointId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); - uint32_t clusterId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); - uint32_t eventId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(eventIdObj, kInvalidEventId, eventId)); - - outEndpointId = static_cast(endpointId); - outClusterId = static_cast(clusterId); - outEventId = static_cast(eventId); - outIsUrgent = (isUrgent == JNI_TRUE); - - return CHIP_NO_ERROR; -} - -/** - * Takes objects in dataVersionFilterList, converts them to app:DataVersionFilter, and appends them to outDataVersionFilterList. - */ -CHIP_ERROR ParseDataVersionFilterList(jobject dataVersionFilterList, std::vector & outDataVersionFilterList) -{ - jint listSize; - - if (dataVersionFilterList == nullptr) - { - return CHIP_NO_ERROR; - } - - ReturnErrorOnFailure(JniReferences::GetInstance().GetListSize(dataVersionFilterList, listSize)); - - for (uint8_t i = 0; i < listSize; i++) - { - jobject dataVersionFilterItem = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().GetListItem(dataVersionFilterList, i, dataVersionFilterItem)); - - EndpointId endpointId; - ClusterId clusterId; - DataVersion dataVersion; - ReturnErrorOnFailure(ParseDataVersionFilter(dataVersionFilterItem, endpointId, clusterId, dataVersion)); - outDataVersionFilterList.push_back(app::DataVersionFilter(endpointId, clusterId, dataVersion)); + ChipLogError(Controller, "JNI IM Invoke Error: %" CHIP_ERROR_FORMAT, err.Format()); } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR ParseDataVersionFilter(jobject versionFilter, EndpointId & outEndpointId, ClusterId & outClusterId, - DataVersion & outDataVersion) -{ - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - jmethodID getEndpointIdMethod = nullptr; - jmethodID getClusterIdMethod = nullptr; - jmethodID getDataVersionMethod = nullptr; - - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, versionFilter, "getEndpointId", "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, versionFilter, "getClusterId", "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); - ReturnErrorOnFailure( - JniReferences::GetInstance().FindMethod(env, versionFilter, "getDataVersion", "()J", &getDataVersionMethod)); - - jobject endpointIdObj = env->CallObjectMethod(versionFilter, getEndpointIdMethod); - VerifyOrReturnError(endpointIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - uint32_t endpointId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); - outEndpointId = static_cast(endpointId); - jobject clusterIdObj = env->CallObjectMethod(versionFilter, getClusterIdMethod); - VerifyOrReturnError(clusterIdObj != nullptr, CHIP_ERROR_INCORRECT_STATE); - uint32_t clusterId = 0; - ReturnErrorOnFailure(GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); - outClusterId = static_cast(clusterId); - - outDataVersion = static_cast(env->CallLongMethod(versionFilter, getDataVersionMethod)); - return CHIP_NO_ERROR; -} - -CHIP_ERROR GetChipPathIdValue(jobject chipPathId, uint32_t wildcardValue, uint32_t & outValue) -{ - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - bool idIsWildcard = false; - ReturnErrorOnFailure(IsWildcardChipPathId(chipPathId, idIsWildcard)); - - if (idIsWildcard) - { - outValue = wildcardValue; - return CHIP_NO_ERROR; - } - - jmethodID getIdMethod = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, chipPathId, "getId", "()J", &getIdMethod)); - outValue = static_cast(env->CallLongMethod(chipPathId, getIdMethod)); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - - return CHIP_NO_ERROR; -} - -CHIP_ERROR IsWildcardChipPathId(jobject chipPathId, bool & isWildcard) -{ - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - jmethodID getTypeMethod = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod( - env, chipPathId, "getType", "()Lchip/devicecontroller/model/ChipPathId$IdType;", &getTypeMethod)); - - jobject idType = env->CallObjectMethod(chipPathId, getTypeMethod); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrReturnError(idType != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); - - jmethodID nameMethod = nullptr; - ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, idType, "name", "()Ljava/lang/String;", &nameMethod)); - - jstring typeNameString = static_cast(env->CallObjectMethod(idType, nameMethod)); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - VerifyOrReturnError(typeNameString != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); - - JniUtfString typeNameJniString(env, typeNameString); - - isWildcard = strncmp(typeNameJniString.c_str(), "WILDCARD", 8) == 0; - - return CHIP_NO_ERROR; } void * IOThreadMain(void * arg) diff --git a/src/controller/java/MatterCallbacks-JNI.cpp b/src/controller/java/MatterCallbacks-JNI.cpp new file mode 100644 index 00000000000000..f20d9e47f093ff --- /dev/null +++ b/src/controller/java/MatterCallbacks-JNI.cpp @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#include "AndroidCallbacks.h" + +#include +#include +#include +#include + +#define JNI_METHOD(RETURN, CLASS_NAME, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_matter_controller_##CLASS_NAME##_##METHOD_NAME + +using namespace chip::Controller; + +JNI_METHOD(jlong, GetConnectedDeviceCallbackJni, newCallback)(JNIEnv * env, jobject self, jobject callback) +{ + return newConnectedDeviceCallback(env, self, callback); +} + +JNI_METHOD(void, GetConnectedDeviceCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) +{ + deleteConnectedDeviceCallback(env, self, callbackHandle); +} + +JNI_METHOD(jlong, ReportCallbackJni, newCallback) +(JNIEnv * env, jobject self, jobject subscriptionEstablishedCallbackJava, jobject resubscriptionAttemptCallbackJava) +{ + return newReportCallback(env, self, subscriptionEstablishedCallbackJava, resubscriptionAttemptCallbackJava, + "()Lmatter/controller/model/NodeState;"); +} + +JNI_METHOD(void, ReportCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) +{ + deleteReportCallback(env, self, callbackHandle); +} + +JNI_METHOD(jlong, WriteAttributesCallbackJni, newCallback) +(JNIEnv * env, jobject self) +{ + return newWriteAttributesCallback(env, self); +} + +JNI_METHOD(void, WriteAttributesCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) +{ + deleteWriteAttributesCallback(env, self, callbackHandle); +} + +JNI_METHOD(jlong, InvokeCallbackJni, newCallback) +(JNIEnv * env, jobject self) +{ + return newInvokeCallback(env, self); +} + +JNI_METHOD(void, InvokeCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) +{ + deleteInvokeCallback(env, self, callbackHandle); +} diff --git a/src/controller/java/MatterController-JNI.cpp b/src/controller/java/MatterController-JNI.cpp new file mode 100644 index 00000000000000..6a541723d041ed --- /dev/null +++ b/src/controller/java/MatterController-JNI.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 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. + * + */ +#include "AndroidInteractionClient.h" + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_matter_controller_MatterControllerImpl_##METHOD_NAME + +JNI_METHOD(void, subscribe) +(JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, + jint minInterval, jint maxInterval, jboolean keepSubscriptions, jboolean isFabricFiltered, jint imTimeoutMs) +{ + CHIP_ERROR err = subscribe(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, nullptr, minInterval, + maxInterval, keepSubscriptions, isFabricFiltered, imTimeoutMs, nullptr); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Subscribe Error: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + +JNI_METHOD(void, read) +(JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributePathList, jobject eventPathList, + jboolean isFabricFiltered, jint imTimeoutMs) +{ + CHIP_ERROR err = read(env, handle, callbackHandle, devicePtr, attributePathList, eventPathList, nullptr, isFabricFiltered, + imTimeoutMs, nullptr); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Read Error: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + +JNI_METHOD(void, write) +(JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, jint timedRequestTimeoutMs, + jint imTimeoutMs) +{ + CHIP_ERROR err = write(env, handle, callbackHandle, devicePtr, attributeList, timedRequestTimeoutMs, imTimeoutMs); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Write Error: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + +JNI_METHOD(void, invoke) +(JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject invokeElement, jint timedRequestTimeoutMs, + jint imTimeoutMs) +{ + CHIP_ERROR err = invoke(env, handle, callbackHandle, devicePtr, invokeElement, timedRequestTimeoutMs, imTimeoutMs); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "JNI IM Invoke Error: %" CHIP_ERROR_FORMAT, err.Format()); + } +} diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index bbf247b09e5a9d..9e5c6e12f42520 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -51,6 +51,11 @@ public static void loadJni() { return; } + // temp, for kotlin_library + public long getDeviceControllerPtr() { + return deviceControllerPtr; + } + /** * Returns a new {@link ChipDeviceController} with the specified parameters. you must set a vendor * ID, ControllerParams.newBuilder().setControllerVendorId(0xFFF4).build() 0xFFF4 is a test vendor diff --git a/src/controller/java/src/chip/devicecontroller/InvokeCallbackJni.java b/src/controller/java/src/chip/devicecontroller/InvokeCallbackJni.java index 926aa0088c21bb..ceb35eccefd9b0 100644 --- a/src/controller/java/src/chip/devicecontroller/InvokeCallbackJni.java +++ b/src/controller/java/src/chip/devicecontroller/InvokeCallbackJni.java @@ -17,6 +17,8 @@ */ package chip.devicecontroller; +import chip.devicecontroller.model.InvokeElement; + /** JNI wrapper callback class for {@link InvokeCallback}. */ public final class InvokeCallbackJni { private final InvokeCallback wrappedInvokeCallback; @@ -24,17 +26,36 @@ public final class InvokeCallbackJni { public InvokeCallbackJni(InvokeCallback wrappedInvokeCallback) { this.wrappedInvokeCallback = wrappedInvokeCallback; - this.callbackHandle = newCallback(wrappedInvokeCallback); + this.callbackHandle = newCallback(); } long getCallbackHandle() { return callbackHandle; } - private native long newCallback(InvokeCallback wrappedCallback); + private native long newCallback(); private native void deleteCallback(long callbackHandle); + private void onError(Exception e) { + wrappedInvokeCallback.onError(e); + } + + private void onResponse( + int endpointId, + long clusterId, + long commandId, + byte[] tlv, + String jsonString, + long successCode) { + wrappedInvokeCallback.onResponse( + InvokeElement.newInstance(endpointId, clusterId, commandId, tlv, jsonString), successCode); + } + + private void onDone() { + wrappedInvokeCallback.onDone(); + } + // TODO(#8578): Replace finalizer with PhantomReference. @SuppressWarnings("deprecation") protected void finalize() throws Throwable { diff --git a/src/controller/java/src/chip/devicecontroller/ReportCallbackJni.java b/src/controller/java/src/chip/devicecontroller/ReportCallbackJni.java index b5a002508d5add..4f2529fa8d58b0 100644 --- a/src/controller/java/src/chip/devicecontroller/ReportCallbackJni.java +++ b/src/controller/java/src/chip/devicecontroller/ReportCallbackJni.java @@ -17,6 +17,9 @@ */ package chip.devicecontroller; +import chip.devicecontroller.model.ChipAttributePath; +import chip.devicecontroller.model.ChipEventPath; +import chip.devicecontroller.model.NodeState; import javax.annotation.Nullable; /** JNI wrapper callback class for {@link ReportCallback}. */ @@ -25,6 +28,7 @@ public class ReportCallbackJni { @Nullable private ResubscriptionAttemptCallback wrappedResubscriptionAttemptCallback; private ReportCallback wrappedReportCallback; private long callbackHandle; + @Nullable private NodeState nodeState; public ReportCallbackJni( @Nullable SubscriptionEstablishedCallback subscriptionEstablishedCallback, @@ -34,7 +38,7 @@ public ReportCallbackJni( this.wrappedReportCallback = reportCallback; this.wrappedResubscriptionAttemptCallback = resubscriptionAttemptCallback; this.callbackHandle = - newCallback(subscriptionEstablishedCallback, reportCallback, resubscriptionAttemptCallback); + newCallback(subscriptionEstablishedCallback, resubscriptionAttemptCallback); } long getCallbackHandle() { @@ -43,11 +47,48 @@ long getCallbackHandle() { private native long newCallback( @Nullable SubscriptionEstablishedCallback subscriptionEstablishedCallback, - ReportCallback wrappedCallback, @Nullable ResubscriptionAttemptCallback resubscriptionAttemptCallback); private native void deleteCallback(long callbackHandle); + // Called from native code only, which ignores access modifiers. + private void onReportBegin() { + nodeState = new NodeState(); + } + + private void onReportEnd() { + if (nodeState != null) { + wrappedReportCallback.onReport(nodeState); + } + nodeState = null; + } + + private NodeState getNodeState() { + return nodeState; + } + + private void onError( + boolean isAttributePath, + int attributeEndpointId, + long attributeClusterId, + long attributeId, + boolean isEventPath, + int eventEndpointId, + long eventClusterId, + long eventId, + Exception e) { + wrappedReportCallback.onError( + isAttributePath + ? ChipAttributePath.newInstance(attributeEndpointId, attributeClusterId, attributeId) + : null, + isEventPath ? ChipEventPath.newInstance(eventEndpointId, eventClusterId, eventId) : null, + e); + } + + private void onDone() { + wrappedReportCallback.onDone(); + } + // TODO(#8578): Replace finalizer with PhantomReference. @SuppressWarnings("deprecation") protected void finalize() throws Throwable { diff --git a/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java index 38f98c3201103a..fd7f8066c80a73 100644 --- a/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java +++ b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java @@ -17,6 +17,8 @@ */ package chip.devicecontroller; +import chip.devicecontroller.model.ChipAttributePath; + /** JNI wrapper callback class for {@link WriteAttributesCallback}. */ public final class WriteAttributesCallbackJni { private final WriteAttributesCallback wrappedWriteAttributesCallback; @@ -24,17 +26,34 @@ public final class WriteAttributesCallbackJni { public WriteAttributesCallbackJni(WriteAttributesCallback wrappedWriteAttributesCallback) { this.wrappedWriteAttributesCallback = wrappedWriteAttributesCallback; - this.callbackHandle = newCallback(wrappedWriteAttributesCallback); + this.callbackHandle = newCallback(); } long getCallbackHandle() { return callbackHandle; } - private native long newCallback(WriteAttributesCallback wrappedCallback); + private native long newCallback(); private native void deleteCallback(long callbackHandle); + // Called from native code only, which ignores access modifiers. + private void onError( + boolean isAttributePath, int endpointId, long clusterId, long attributeId, Exception e) { + wrappedWriteAttributesCallback.onError( + isAttributePath ? ChipAttributePath.newInstance(endpointId, clusterId, attributeId) : null, + e); + } + + private void onResponse(int endpointId, long clusterId, long attributeId) { + wrappedWriteAttributesCallback.onResponse( + ChipAttributePath.newInstance(endpointId, clusterId, attributeId)); + } + + private void onDone() { + wrappedWriteAttributesCallback.onDone(); + } + // TODO(#8578): Replace finalizer with PhantomReference. @SuppressWarnings("deprecation") protected void finalize() throws Throwable { diff --git a/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java b/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java index d19ad407b1f4db..2a3310890a9015 100644 --- a/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java +++ b/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java @@ -77,6 +77,19 @@ public ChipPathId getAttributeId() { return attributeId; } + // For use in JNI. + long getEndpointId(long wildcardValue) { + return endpointId.getId(wildcardValue); + } + + long getClusterId(long wildcardValue) { + return clusterId.getId(wildcardValue); + } + + long getAttributeId(long wildcardValue) { + return attributeId.getId(wildcardValue); + } + public int getDataVersion() { return dataVersion.orElse(0); } diff --git a/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java b/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java index f4b085f0350de4..0af1a87de5a523 100644 --- a/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java +++ b/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java @@ -30,6 +30,12 @@ private ChipAttributePath(ChipPathId endpointId, ChipPathId clusterId, ChipPathI this.attributeId = attributeId; } + ChipAttributePath(int endpointId, long clusterId, long attributeId) { + this.endpointId = ChipPathId.forId(endpointId); + this.clusterId = ChipPathId.forId(clusterId); + this.attributeId = ChipPathId.forId(attributeId); + } + public ChipPathId getEndpointId() { return endpointId; } @@ -42,6 +48,19 @@ public ChipPathId getAttributeId() { return attributeId; } + // For use in JNI. + private long getEndpointId(long wildcardValue) { + return endpointId.getId(wildcardValue); + } + + private long getClusterId(long wildcardValue) { + return clusterId.getId(wildcardValue); + } + + private long getAttributeId(long wildcardValue) { + return attributeId.getId(wildcardValue); + } + @Override public boolean equals(Object object) { if (object instanceof ChipAttributePath) { diff --git a/src/controller/java/src/chip/devicecontroller/model/ChipEventPath.java b/src/controller/java/src/chip/devicecontroller/model/ChipEventPath.java index 2f70a97f81d614..54ae737ca4d1a0 100644 --- a/src/controller/java/src/chip/devicecontroller/model/ChipEventPath.java +++ b/src/controller/java/src/chip/devicecontroller/model/ChipEventPath.java @@ -33,6 +33,13 @@ private ChipEventPath( this.isUrgent = isUrgent; } + ChipEventPath(int endpointId, long clusterId, long eventId, boolean isUrgent) { + this.endpointId = ChipPathId.forId(endpointId); + this.clusterId = ChipPathId.forId(clusterId); + this.eventId = ChipPathId.forId(eventId); + this.isUrgent = isUrgent; + } + public ChipPathId getEndpointId() { return endpointId; } @@ -45,6 +52,19 @@ public ChipPathId getEventId() { return eventId; } + // For use in JNI. + private long getEndpointId(long wildcardValue) { + return endpointId.getId(wildcardValue); + } + + private long getClusterId(long wildcardValue) { + return clusterId.getId(wildcardValue); + } + + private long getEventId(long wildcardValue) { + return eventId.getId(wildcardValue); + } + public boolean isUrgent() { return isUrgent; } diff --git a/src/controller/java/src/chip/devicecontroller/model/ChipPathId.java b/src/controller/java/src/chip/devicecontroller/model/ChipPathId.java index 31511d7eda462b..d7b44994553d47 100644 --- a/src/controller/java/src/chip/devicecontroller/model/ChipPathId.java +++ b/src/controller/java/src/chip/devicecontroller/model/ChipPathId.java @@ -43,6 +43,11 @@ public IdType getType() { return type; } + // For use in JNI. + long getId(long wildCardValue) { + return type == IdType.WILDCARD ? wildCardValue : id; + } + @Override public boolean equals(Object object) { if (object instanceof ChipPathId) { diff --git a/src/controller/java/src/chip/devicecontroller/model/DataVersionFilter.java b/src/controller/java/src/chip/devicecontroller/model/DataVersionFilter.java index 9eb9aa7b8e407a..e4e0d904873ec5 100644 --- a/src/controller/java/src/chip/devicecontroller/model/DataVersionFilter.java +++ b/src/controller/java/src/chip/devicecontroller/model/DataVersionFilter.java @@ -46,6 +46,15 @@ public long getDataVersion() { return dataVersion; } + // For use in JNI. + private long getEndpointId(long wildcardValue) { + return endpointId.getId(wildcardValue); + } + + private long getClusterId(long wildcardValue) { + return clusterId.getId(wildcardValue); + } + // check whether the current DataVersionFilter has same path as others. @Override public boolean equals(Object object) { diff --git a/src/controller/java/src/chip/devicecontroller/model/InvokeElement.java b/src/controller/java/src/chip/devicecontroller/model/InvokeElement.java index b56aae75c2c22f..71c68cb4d203f5 100644 --- a/src/controller/java/src/chip/devicecontroller/model/InvokeElement.java +++ b/src/controller/java/src/chip/devicecontroller/model/InvokeElement.java @@ -77,6 +77,19 @@ public ChipPathId getCommandId() { return commandId; } + // For use in JNI. + private long getEndpointId(long wildcardValue) { + return endpointId.getId(wildcardValue); + } + + private long getClusterId(long wildcardValue) { + return clusterId.getId(wildcardValue); + } + + private long getCommandId(long wildcardValue) { + return commandId.getId(wildcardValue); + } + public Optional getGroupId() { return groupId; } diff --git a/src/controller/java/src/chip/devicecontroller/model/NodeState.java b/src/controller/java/src/chip/devicecontroller/model/NodeState.java index f77097b5452f62..aaa90c3f1a1d93 100644 --- a/src/controller/java/src/chip/devicecontroller/model/NodeState.java +++ b/src/controller/java/src/chip/devicecontroller/model/NodeState.java @@ -35,6 +35,57 @@ public Map getEndpointStates() { } // Called from native code only, which ignores access modifiers. + private void addAttribute( + int endpointId, + long clusterId, + long attributeId, + Object valueObject, + byte[] tlv, + String jsonString) { + addAttribute( + endpointId, clusterId, attributeId, new AttributeState(valueObject, tlv, jsonString)); + } + + private void addEvent( + int endpointId, + long clusterId, + long eventId, + long eventNumber, + int priorityLevel, + int timestampType, + long timestampValue, + Object valueObject, + byte[] tlv, + String jsonString) { + addEvent( + endpointId, + clusterId, + eventId, + new EventState( + eventNumber, + priorityLevel, + timestampType, + timestampValue, + valueObject, + tlv, + jsonString)); + } + + private void addAttributeStatus( + int endpointId, + long clusterId, + long attributeId, + int status, + @Nullable Integer clusterStatus) { + addAttributeStatus( + endpointId, clusterId, attributeId, Status.newInstance(status, clusterStatus)); + } + + private void addEventStatus( + int endpointId, long clusterId, long eventId, int status, @Nullable Integer clusterStatus) { + addEventStatus(endpointId, clusterId, eventId, Status.newInstance(status, clusterStatus)); + } + private void setDataVersion(int endpointId, long clusterId, long dataVersion) { EndpointState endpointState = getEndpointState(endpointId); ClusterState clusterState = endpointState.getClusterState(clusterId); @@ -44,7 +95,6 @@ private void setDataVersion(int endpointId, long clusterId, long dataVersion) { } } - // Called from native code only, which ignores access modifiers. private void addAttribute( int endpointId, long clusterId, long attributeId, AttributeState attributeStateToAdd) { EndpointState endpointState = getEndpointState(endpointId); @@ -93,7 +143,6 @@ private void addEvent(int endpointId, long clusterId, long eventId, EventState e clusterState.getEventStates().get(eventId).add(eventStateToAdd); } - // Called from native code only, which ignores access modifiers. private void addAttributeStatus( int endpointId, long clusterId, long attributeId, Status statusToAdd) { EndpointState endpointState = getEndpointState(endpointId); @@ -112,7 +161,7 @@ private void addAttributeStatus( if (clusterState.getAttributeStates().containsKey(attributeId)) { clusterState.getAttributeStates().remove(attributeId); } - + System.out.println("put : " + attributeId + ", " + statusToAdd); clusterState.getAttributeStatuses().put(attributeId, statusToAdd); } diff --git a/src/controller/java/src/chip/devicecontroller/model/Status.java b/src/controller/java/src/chip/devicecontroller/model/Status.java index 7391f9a312f3f7..af97d84d9a099a 100644 --- a/src/controller/java/src/chip/devicecontroller/model/Status.java +++ b/src/controller/java/src/chip/devicecontroller/model/Status.java @@ -54,4 +54,8 @@ public static Status newInstance(int status, int clusterStatus) { public static Status newInstance(int status) { return new Status(status, Optional.empty()); } + + static Status newInstance(int status, Integer clusterStatus) { + return new Status(status, Optional.ofNullable(clusterStatus)); + } } diff --git a/src/controller/java/src/matter/controller/InvokeCallback.kt b/src/controller/java/src/matter/controller/InvokeCallback.kt new file mode 100644 index 00000000000000..059a282dd654a6 --- /dev/null +++ b/src/controller/java/src/matter/controller/InvokeCallback.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +import java.lang.Exception + +/** An interface for receiving invoke response. */ +interface InvokeCallback { + /** + * OnError will be called when an error occurs after failing to call + * + * @param Exception The IllegalStateException which encapsulated the error message, the possible + * chip error could be - CHIP_ERROR_TIMEOUT: A response was not received within the expected + * response timeout. - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from + * the server. - CHIP_ERROR encapsulating the converted error from the StatusIB: If we got a + * non-path-specific status response from the server. - CHIP_ERROR*: All other cases. + */ + fun onError(ex: Exception) + + /** + * OnResponse will be called when a invoke response has been received and processed for the given + * path. + * + * @param invokeElement The invoke element in invoke response that could have null or nonNull tlv + * data + * @param successCode If data in InvokeElment is null, successCode can be any success status, + * including possibly a cluster-specific one. If data in InvokeElement is not null, successCode + * will always be a generic SUCCESS(0) with no-cluster specific information + */ + fun onResponse(invokeResponse: InvokeResponse?, successCode: Long) + + fun onDone() {} +} diff --git a/src/controller/java/src/matter/controller/InvokeCallbackJni.kt b/src/controller/java/src/matter/controller/InvokeCallbackJni.kt new file mode 100644 index 00000000000000..ac0e5e3ecd4209 --- /dev/null +++ b/src/controller/java/src/matter/controller/InvokeCallbackJni.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +import matter.controller.model.CommandPath + +/** JNI wrapper callback class for [InvokeCallback]. */ +class InvokeCallbackJni(val wrappedInvokeCallback: InvokeCallback) { + private var callbackHandle: Long + + init { + this.callbackHandle = newCallback() + } + + private external fun newCallback(): Long + + private external fun deleteCallback(callbackHandle: Long) + + fun getJniHandle(): Long { + return callbackHandle + } + + private fun onError(e: Exception) { + wrappedInvokeCallback.onError(e) + } + + private fun onResponse( + endpointId: Int, + clusterId: Long, + commandId: Long, + tlv: ByteArray, + jsonString: String, + successCode: Long + ) { + wrappedInvokeCallback.onResponse( + InvokeResponse( + tlv, + CommandPath(endpointId.toUShort(), clusterId.toUInt(), commandId.toUInt()), + jsonString + ), + successCode + ) + } + + private fun onDone() { + wrappedInvokeCallback.onDone() + } + + // TODO(#8578): Replace finalizer with PhantomReference. + @Suppress("deprecation") + @Throws(Throwable::class) + protected fun finalize() { + if (callbackHandle != 0L) { + deleteCallback(callbackHandle) + callbackHandle = 0 + } + } +} diff --git a/src/controller/java/src/matter/controller/MatterControllerImpl.kt b/src/controller/java/src/matter/controller/MatterControllerImpl.kt index fdc1a4c769deeb..202d97a99e1ad0 100644 --- a/src/controller/java/src/matter/controller/MatterControllerImpl.kt +++ b/src/controller/java/src/matter/controller/MatterControllerImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Project CHIP Authors + * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,16 +20,6 @@ package matter.controller import chip.devicecontroller.ChipDeviceController import chip.devicecontroller.ChipDeviceControllerException import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback -import chip.devicecontroller.InvokeCallback -import chip.devicecontroller.ReportCallback -import chip.devicecontroller.ResubscriptionAttemptCallback -import chip.devicecontroller.SubscriptionEstablishedCallback -import chip.devicecontroller.WriteAttributesCallback -import chip.devicecontroller.model.AttributeWriteRequest -import chip.devicecontroller.model.ChipAttributePath -import chip.devicecontroller.model.ChipEventPath -import chip.devicecontroller.model.ChipPathId -import chip.devicecontroller.model.InvokeElement import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.resume @@ -43,9 +33,6 @@ import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.suspendCancellableCoroutine import matter.controller.model.AttributePath -import matter.controller.model.AttributeState -import matter.controller.model.ClusterState -import matter.controller.model.EndpointState import matter.controller.model.EventPath import matter.controller.model.EventState import matter.controller.model.NodeState @@ -54,6 +41,7 @@ import matter.controller.model.NodeState class MatterControllerImpl(params: ControllerParams) : MatterController { private val deviceController: ChipDeviceController private var nodeId: Long? = null + private val deviceControllerPtr: Long override fun setCompletionListener(listener: MatterController.CompletionListener?) = deviceController.setCompletionListener(CompletionListenerAdapter.from(listener)) @@ -89,8 +77,8 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { val nodeId = this.nodeId check(nodeId != null) { "nodeId has not been initialized yet" } - val attributePaths = generateAttributePaths(request) - val eventPaths = generateEventPaths(request) + val attributePaths = request.attributePaths + val eventPaths = request.eventPaths val successes = mutableListOf() val failures = mutableListOf() @@ -130,31 +118,23 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { val reportHandler = object : ReportCallback { - override fun onReport(nodeState: chip.devicecontroller.model.NodeState) { - logger.log(Level.INFO, "Received subscribe update report") - - val tmpNodeState: NodeState = nodeState.wrap() - - for (endpoint in tmpNodeState.endpoints) { + override fun onReport(nodeState: NodeState) { + logger.log(Level.FINE, "Received subscribe report") + for (endpoint in nodeState.endpoints) { for (cluster in endpoint.value.clusters) { for (attribute in cluster.value.attributes) { - val attributePath = - AttributePath( - endpointId = endpoint.key.toUShort(), - clusterId = cluster.key.toUInt(), - attributeId = attribute.key.toUInt() - ) - val readData = ReadData.Attribute(attributePath, attribute.value.tlvValue) + val readData = + ReadData.Attribute(attribute.value.path, attribute.value.tlvValue) successes.add(readData) } for (eventList in cluster.value.events) { for (event in eventList.value) { val timestamp: Timestamp = - when (event.timestampType) { - chip.devicecontroller.model.EventState.MILLIS_SINCE_BOOT -> + when (event.getTimestampType()) { + EventState.TypeStampTypeEnum.MILLIS_SINCE_BOOT -> Timestamp.MillisSinceBoot(event.timestampValue) - chip.devicecontroller.model.EventState.MILLIS_SINCE_EPOCH -> + EventState.TypeStampTypeEnum.MILLIS_SINCE_EPOCH -> Timestamp.MillisSinceEpoch(event.timestampValue) else -> { logger.log(Level.SEVERE, "Unsupported event timestamp type - ignoring") @@ -162,16 +142,9 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } - val eventPath = - EventPath( - endpointId = endpoint.key.toUShort(), - clusterId = cluster.key.toUInt(), - eventId = eventList.key.toUInt() - ) - val readData = ReadData.Event( - path = eventPath, + path = event.path, eventNumber = event.eventNumber.toULong(), priorityLevel = event.priorityLevel.toUByte(), timeStamp = timestamp, @@ -182,7 +155,6 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } } - trySendBlocking(SubscriptionState.NodeStateUpdate(ReadResponse(successes, failures))) .onFailure { ex -> logger.log(Level.SEVERE, "Error sending NodeStateUpdate to subscriber: %s", ex) @@ -190,25 +162,20 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } override fun onError( - attributePath: ChipAttributePath?, - eventPath: ChipEventPath?, - ex: Exception + attributePath: AttributePath?, + eventPath: EventPath?, + e: Exception ) { attributePath?.let { logger.log(Level.INFO, "Report error for attributePath:%s", it.toString()) - val tmpAttributePath: AttributePath = attributePath.wrap() - val attributeFailure = ReadFailure.Attribute(path = tmpAttributePath, error = ex) + val attributeFailure = ReadFailure.Attribute(path = it, error = e) failures.add(attributeFailure) } eventPath?.let { logger.log(Level.INFO, "Report error for eventPath:%s", it.toString()) - val tmpEventPath: EventPath = eventPath.wrap() - val eventFailure = ReadFailure.Event(path = tmpEventPath, error = ex) + val eventFailure = ReadFailure.Event(path = it, error = e) failures.add(eventFailure) } - - // The underlying subscription is terminated if both attributePath & eventPath are - // null if (attributePath == null && eventPath == null) { logger.log(Level.SEVERE, "The underlying subscription is terminated") @@ -226,14 +193,19 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } override fun onDone() { - logger.log(Level.INFO, "Subscription update completed") + logger.log(Level.FINE, "subscribe command completed") } } - deviceController.subscribeToPath( - subscriptionEstablishedHandler, - resubscriptionAttemptHandler, - reportHandler, + val reportCallbackJni = + ReportCallbackJni( + subscriptionEstablishedHandler, + reportHandler, + resubscriptionAttemptHandler + ) + subscribe( + deviceControllerPtr, + reportCallbackJni.getJniHandle(), devicePtr, attributePaths, eventPaths, @@ -249,6 +221,19 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { .buffer(capacity = UNLIMITED) } + private external fun subscribe( + handle: Long, + callbackHandle: Long, + devicePtr: Long, + attributePathList: List, + eventPathList: List, + minInterval: Int, + maxInterval: Int, + keepSubscriptions: Boolean, + isFabricFiltered: Boolean, + imTimeoutMs: Int + ) + override suspend fun read(request: ReadRequest): ReadResponse { // To prevent potential issues related to concurrent modification, assign // the value of the mutable property 'nodeId' to a temporary variable. @@ -257,69 +242,38 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { val devicePtr: Long = getConnectedDevicePointer(nodeId) - val chipAttributePaths = - request.attributePaths.map { attributePath -> - val endpointId = attributePath.endpointId.toInt() - val clusterId = attributePath.clusterId.toLong() - val attributeId = attributePath.attributeId.toLong() - ChipAttributePath.newInstance(endpointId, clusterId, attributeId) - } - - val chipEventPaths = - request.eventPaths.map { eventPath -> - val endpointId = eventPath.endpointId.toInt() - val clusterId = eventPath.clusterId.toLong() - val eventId = eventPath.eventId.toLong() - ChipEventPath.newInstance(endpointId, clusterId, eventId) - } - val successes = mutableListOf() val failures = mutableListOf() return suspendCancellableCoroutine { continuation -> val reportCallback = object : ReportCallback { - override fun onReport(nodeState: chip.devicecontroller.model.NodeState) { + override fun onReport(nodeState: NodeState) { logger.log(Level.FINE, "Received read report") - - val tmpNodeState: NodeState = nodeState.wrap() - - for (endpoint in tmpNodeState.endpoints) { + for (endpoint in nodeState.endpoints) { for (cluster in endpoint.value.clusters) { for (attribute in cluster.value.attributes) { - val attributePath = - AttributePath( - endpointId = endpoint.key.toUShort(), - clusterId = cluster.key.toUInt(), - attributeId = attribute.key.toUInt() - ) - val readData = ReadData.Attribute(attributePath, attribute.value.tlvValue) + val readData = ReadData.Attribute(attribute.value.path, attribute.value.tlvValue) successes.add(readData) } for (eventList in cluster.value.events) { for (event in eventList.value) { val timestamp: Timestamp = - when (event.timestampType) { - chip.devicecontroller.model.EventState.MILLIS_SINCE_BOOT -> + when (event.getTimestampType()) { + EventState.TypeStampTypeEnum.MILLIS_SINCE_BOOT -> Timestamp.MillisSinceBoot(event.timestampValue) - chip.devicecontroller.model.EventState.MILLIS_SINCE_EPOCH -> + EventState.TypeStampTypeEnum.MILLIS_SINCE_EPOCH -> Timestamp.MillisSinceEpoch(event.timestampValue) else -> { logger.log(Level.SEVERE, "Unsupported event timestamp type - ignoring") break } } - val eventPath = - EventPath( - endpointId = endpoint.key.toUShort(), - clusterId = cluster.key.toUInt(), - eventId = eventList.key.toUInt() - ) val readData = ReadData.Event( - path = eventPath, + path = event.path, eventNumber = event.eventNumber.toULong(), priorityLevel = event.priorityLevel.toUByte(), timeStamp = timestamp, @@ -332,30 +286,17 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } - override fun onError( - attributePath: ChipAttributePath?, - eventPath: ChipEventPath?, - ex: Exception - ) { + override fun onError(attributePath: AttributePath?, eventPath: EventPath?, e: Exception) { attributePath?.let { logger.log(Level.INFO, "Report error for attributePath:%s", it.toString()) - val tmpAttributePath: AttributePath = attributePath.wrap() - val attributeFailure = ReadFailure.Attribute(path = tmpAttributePath, error = ex) + val attributeFailure = ReadFailure.Attribute(path = it, error = e) failures.add(attributeFailure) } eventPath?.let { logger.log(Level.INFO, "Report error for eventPath:%s", it.toString()) - val tmpEventPath: EventPath = eventPath.wrap() - val eventFailure = ReadFailure.Event(path = tmpEventPath, error = ex) + val eventFailure = ReadFailure.Event(path = it, error = e) failures.add(eventFailure) } - - // The underlying subscription is terminated if both attributePath & eventPath are null - if (attributePath == null && eventPath == null) { - continuation.resumeWithException( - Exception("Read command failed with error ${ex.message}") - ) - } } override fun onDone() { @@ -363,18 +304,29 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { continuation.resume(ReadResponse(successes, failures)) } } - - deviceController.readPath( - reportCallback, + val reportCallbackJni = ReportCallbackJni(null, reportCallback, null) + read( + deviceControllerPtr, + reportCallbackJni.getJniHandle(), devicePtr, - chipAttributePaths, - chipEventPaths, + request.attributePaths, + request.eventPaths, false, - CHIP_IM_TIMEOUT_MS, + CHIP_IM_TIMEOUT_MS ) } } + private external fun read( + handle: Long, + callbackHandle: Long, + devicePtr: Long, + attributePathList: List, + eventPathList: List, + isFabricFiltered: Boolean, + imTimeoutMs: Int + ) + override suspend fun write(writeRequests: WriteRequests): WriteResponse { // To prevent potential issues related to concurrent modification, assign // the value of the mutable property 'nodeId' to a temporary variable. @@ -383,26 +335,16 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { val devicePtr: Long = getConnectedDevicePointer(nodeId) - val attributeWriteRequests = - writeRequests.requests.map { request -> - AttributeWriteRequest.newInstance( - ChipPathId.forId(request.attributePath.endpointId.toLong()), - ChipPathId.forId(request.attributePath.clusterId.toLong()), - ChipPathId.forId(request.attributePath.attributeId.toLong()), - request.tlvPayload - ) - } - val failures = mutableListOf() return suspendCancellableCoroutine { continuation -> val writeCallback = object : WriteAttributesCallback { - override fun onResponse(attributePath: ChipAttributePath) { + override fun onResponse(attributePath: AttributePath) { logger.log(Level.INFO, "write success for attributePath:%s", attributePath.toString()) } - override fun onError(attributePath: ChipAttributePath?, ex: Exception) { + override fun onError(attributePath: AttributePath?, ex: Exception) { logger.log( Level.SEVERE, "Failed to write attribute at path: %s", @@ -418,7 +360,7 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { continuation.resumeWithException(ex) } } else { - failures.add(AttributeWriteError(attributePath.wrap(), ex)) + failures.add(AttributeWriteError(attributePath, ex)) } } @@ -432,17 +374,27 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } } - - deviceController.write( - writeCallback, + val writeAttributeCallbackJni = WriteAttributesCallbackJni(writeCallback) + write( + deviceControllerPtr, + writeAttributeCallbackJni.getCallbackHandle(), devicePtr, - attributeWriteRequests.toList(), + writeRequests.requests, writeRequests.timedRequest?.toMillis()?.toInt() ?: 0, CHIP_IM_TIMEOUT_MS, ) } } + private external fun write( + handle: Long, + callbackHandle: Long, + devicePtr: Long, + writeRequestList: List, + timedRequestTimeoutMs: Int, + imTimeoutMs: Int + ) + override suspend fun invoke(request: InvokeRequest): InvokeResponse { // To prevent potential issues related to concurrent modification, assign // the value of the mutable property 'nodeId' to a temporary variable. @@ -451,22 +403,18 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { val devicePtr: Long = getConnectedDevicePointer(nodeId) - val invokeRequest = - InvokeElement.newInstance( - ChipPathId.forId(request.commandPath.endpointId.toLong()), - ChipPathId.forId(request.commandPath.clusterId.toLong()), - ChipPathId.forId(request.commandPath.commandId.toLong()), - request.tlvPayload, - /* jsonString= */ null - ) - return suspendCancellableCoroutine { continuation -> var invokeCallback = object : InvokeCallback { - override fun onResponse(invokeElement: InvokeElement?, successCode: Long) { + override fun onResponse(invokeResponse: InvokeResponse?, successCode: Long) { logger.log(Level.FINE, "Invoke onResponse is received") - val tlvByteArray = invokeElement?.getTlvByteArray() ?: byteArrayOf() - continuation.resume(InvokeResponse(tlvByteArray)) + val ret = + if (invokeResponse == null) { + InvokeResponse(byteArrayOf(), request.commandPath, null) + } else { + invokeResponse + } + continuation.resume(ret) } override fun onError(ex: Exception) { @@ -477,17 +425,27 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } } - - deviceController.invoke( - invokeCallback, + val invokeCallbackJni = InvokeCallbackJni(invokeCallback) + invoke( + deviceControllerPtr, + invokeCallbackJni.getJniHandle(), devicePtr, - invokeRequest, + request, request.timedRequest?.toMillis()?.toInt() ?: 0, CHIP_IM_TIMEOUT_MS ) } } + private external fun invoke( + handle: Long, + callbackHandle: Long, + devicePtr: Long, + invokeRequest: InvokeRequest, + timedRequestTimeoutMs: Int, + imTimeoutMs: Int + ) + override fun close() { logger.log(Level.INFO, "MatterController is closed") deviceController.shutdownCommissioning() @@ -515,68 +473,64 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } } - private fun generateAttributePaths(request: SubscribeRequest): List { - return request.attributePaths.map { attributePath -> - ChipAttributePath.newInstance( - attributePath.endpointId.toInt(), - attributePath.clusterId.toLong(), - attributePath.attributeId.toLong() - ) - } - } - - private fun generateEventPaths(request: SubscribeRequest): List { - return request.eventPaths.map { eventPath -> - ChipEventPath.newInstance( - eventPath.endpointId.toInt(), - eventPath.clusterId.toLong(), - eventPath.eventId.toLong(), - false - ) - } - } - - private fun ChipAttributePath.wrap(): AttributePath { - return AttributePath( - endpointId.getId().toUShort(), - clusterId.getId().toUInt(), - attributeId.getId().toUInt() - ) - } - - private fun ChipEventPath.wrap(): EventPath { - return EventPath( - endpointId.getId().toUShort(), - clusterId.getId().toUInt(), - eventId.getId().toUInt() - ) - } - - private fun chip.devicecontroller.model.NodeState.wrap(): NodeState { - return NodeState( - endpoints = endpointStates.mapValues { (id, value) -> value.wrap(id) }, - ) - } - - private fun chip.devicecontroller.model.EndpointState.wrap(id: Int): EndpointState { - return EndpointState(id, clusterStates.mapValues { (id, value) -> value.wrap(id) }) - } - - private fun chip.devicecontroller.model.ClusterState.wrap(id: Long): ClusterState { - return ClusterState( - id, - attributeStates.mapValues { (id, value) -> value.wrap(id) }, - eventStates.mapValues { (id, value) -> value.map { eventState -> eventState.wrap(id) } } - ) - } - - private fun chip.devicecontroller.model.AttributeState.wrap(id: Long): AttributeState { - return AttributeState(id, tlv, json.toString()) - } - - private fun chip.devicecontroller.model.EventState.wrap(id: Long): EventState { - return EventState(id, eventNumber, priorityLevel, timestampType, timestampValue, tlv) - } + // private fun ChipAttributePath.wrap(): AttributePath { + // return AttributePath( + // endpointId.getId().toUShort(), + // clusterId.getId().toUInt(), + // attributeId.getId().toUInt() + // ) + // } + + // private fun ChipEventPath.wrap(): EventPath { + // return EventPath( + // endpointId.getId().toUShort(), + // clusterId.getId().toUInt(), + // eventId.getId().toUInt() + // ) + // } + + // private fun chip.devicecontroller.model.NodeState.wrap(): NodeState { + // return NodeState( + // endpoints = endpointStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap(), + // ) + // } + + // private fun chip.devicecontroller.model.EndpointState.wrap(id: Int): EndpointState { + // return EndpointState( + // id, + // clusterStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap() + // ) + // } + + // private fun chip.devicecontroller.model.ClusterState.wrap(id: Long): ClusterState { + // return ClusterState( + // id, + // attributeStates.mapValues { (id, value) -> value.wrap(id) }.toMutableMap(), + // eventStates + // .mapValues { (id, value) -> + // value.map { eventState -> eventState.wrap(id) }.toMutableList() + // } + // .toMutableMap() + // ) + // } + + // private fun chip.devicecontroller.model.AttributeState.wrap(id: Long): AttributeState { + // return AttributeState(id, tlv, json.toString(), AttributePath(0U, 0U, id.toUInt()), value) + // } + + // private fun chip.devicecontroller.model.EventState.wrap(id: Long): EventState { + // return EventState( + // id, + // eventNumber, + // priorityLevel, + // timestampType, + // timestampValue, + // tlv, + // EventPath(0U, 0U, id.toUInt()), + // json.toString(), + // value + // ) + // } init { val config: OperationalKeyConfig? = params.operationalKeyConfig @@ -597,6 +551,7 @@ class MatterControllerImpl(params: ControllerParams) : MatterController { } deviceController = ChipDeviceController(paramsBuilder.build()) + deviceControllerPtr = deviceController.deviceControllerPtr } companion object { diff --git a/src/controller/java/src/matter/controller/Messages.kt b/src/controller/java/src/matter/controller/Messages.kt index 36f3fd6fc3ad2c..3d14ef09037342 100644 --- a/src/controller/java/src/matter/controller/Messages.kt +++ b/src/controller/java/src/matter/controller/Messages.kt @@ -18,6 +18,7 @@ package matter.controller import java.time.Duration +import java.util.Optional import matter.controller.model.AttributePath import matter.controller.model.CommandPath import matter.controller.model.EventPath @@ -150,11 +151,40 @@ sealed class SubscriptionState { * * @param attributePath The attribute path information in the write request. * @param tlvPayload The ByteArray representation of the TLV payload. + * @param dataVersion The data version in the write request. */ class WriteRequest( val attributePath: AttributePath, val tlvPayload: ByteArray, -) + val dataVersion: UInt? = null +) { + private fun getEndpointId(wildcardId: Long): Long { + return attributePath.endpointId?.toLong() ?: wildcardId + } + + private fun getClusterId(wildcardId: Long): Long { + return attributePath.clusterId?.toLong() ?: wildcardId + } + + private fun getAttributeId(wildcardId: Long): Long { + return attributePath.attributeId?.toLong() ?: wildcardId + } + + // For JNI interface + private fun getJsonString(): String? = null + + fun getTlvByteArray(): ByteArray { + return tlvPayload + } + + fun hasDataVersion(): Boolean { + return dataVersion != null + } + + fun getDataVersion(): Int { + return dataVersion?.toInt() ?: 0 + } +} /** * Information about a collection of write request elements. @@ -189,8 +219,39 @@ sealed interface WriteResponse { class InvokeRequest( val commandPath: CommandPath, val tlvPayload: ByteArray, - val timedRequest: Duration? -) + val timedRequest: Duration?, + val jsonString: String? = null +) { + private fun getEndpointId(wildcardId: Long): Long { + return commandPath.endpointId?.toLong() ?: wildcardId + } + + @Suppress("UNUSED_PARAMETER") + private fun getClusterId(wildcardId: Long): Long { + return commandPath.clusterId.toLong() + } + + @Suppress("UNUSED_PARAMETER") + private fun getCommandId(wildcardId: Long): Long { + return commandPath.commandId.toLong() + } + + fun getGroupId(): Optional { + return Optional.ofNullable(commandPath.groupId?.toInt()) + } + + fun isEndpointIdValid(): Boolean { + return commandPath.groupId == null + } + + fun isGroupIdValid(): Boolean { + return commandPath.groupId != null + } + + fun getTlvByteArray(): ByteArray { + return tlvPayload + } +} /** * InvokeResponse will be received when a invoke response has been successful received and @@ -198,4 +259,4 @@ class InvokeRequest( * * @param payload An invoke response that could contain tlv data or empty. */ -class InvokeResponse(val payload: ByteArray) +class InvokeResponse(val payload: ByteArray, val path: CommandPath, val jsonString: String? = null) diff --git a/src/controller/java/src/matter/controller/ReportCallback.kt b/src/controller/java/src/matter/controller/ReportCallback.kt new file mode 100644 index 00000000000000..3d05fce04dacf8 --- /dev/null +++ b/src/controller/java/src/matter/controller/ReportCallback.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 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. + * + */ +package matter.controller + +import matter.controller.model.AttributePath +import matter.controller.model.EventPath +import matter.controller.model.NodeState + +/** An interface for receiving read/subscribe CHIP reports. */ +interface ReportCallback { + fun onError(attributePath: AttributePath?, eventPath: EventPath?, e: Exception) + + fun onReport(nodeState: NodeState) + + fun onDone() {} +} diff --git a/src/controller/java/src/matter/controller/ReportCallbackJni.kt b/src/controller/java/src/matter/controller/ReportCallbackJni.kt new file mode 100644 index 00000000000000..ec32df596ae9ff --- /dev/null +++ b/src/controller/java/src/matter/controller/ReportCallbackJni.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +import matter.controller.model.AttributePath +import matter.controller.model.EventPath +import matter.controller.model.NodeState + +/** JNI wrapper callback class for [ReportCallback]. */ +class ReportCallbackJni( + subscriptionEstablishedCallback: SubscriptionEstablishedCallback?, + reportCallback: ReportCallback, + resubscriptionAttemptCallback: ResubscriptionAttemptCallback? +) { + private val wrappedSubscriptionEstablishedCallback: SubscriptionEstablishedCallback? = + subscriptionEstablishedCallback + private val wrappedResubscriptionAttemptCallback: ResubscriptionAttemptCallback? = + resubscriptionAttemptCallback + + private val wrappedReportCallback: ReportCallback = reportCallback + private var callbackHandle: Long + + private var nodeState: NodeState? = null + + init { + this.callbackHandle = + newCallback(subscriptionEstablishedCallback, resubscriptionAttemptCallback) + } + + private external fun newCallback( + subscriptionEstablishedCallback: SubscriptionEstablishedCallback?, + resubscriptionAttemptCallback: ResubscriptionAttemptCallback? + ): Long + + private external fun deleteCallback(callbackHandle: Long) + + fun getJniHandle(): Long { + return callbackHandle + } + + // Called from native code only, which ignores access modifiers. + private fun onReportBegin() { + nodeState = NodeState() + } + + private fun onReportEnd() { + if (nodeState != null) { + wrappedReportCallback.onReport(nodeState!!) + } + nodeState = null + } + + private fun getNodeState(): NodeState? { + return nodeState + } + + private fun onError( + isAttributePath: Boolean, + attributeEndpointId: Int, + attributeClusterId: Long, + attributeId: Long, + isEventPath: Boolean, + eventEndpointId: Int, + eventClusterId: Long, + eventId: Long, + e: Exception + ) { + wrappedReportCallback.onError( + if (isAttributePath) + AttributePath( + attributeEndpointId.toUShort(), + attributeClusterId.toUInt(), + attributeId.toUInt() + ) + else null, + if (isEventPath) + EventPath(eventEndpointId.toUShort(), eventClusterId.toUInt(), eventId.toUInt()) + else null, + e + ) + } + + private fun onDone() { + wrappedReportCallback.onDone() + } + + // TODO(#8578): Replace finalizer with PhantomReference. + @Suppress("deprecation") + @Throws(Throwable::class) + protected fun finalize() { + if (callbackHandle != 0L) { + deleteCallback(callbackHandle) + callbackHandle = 0 + } + } +} diff --git a/src/controller/java/src/matter/controller/ResubscriptionAttemptCallback.kt b/src/controller/java/src/matter/controller/ResubscriptionAttemptCallback.kt new file mode 100644 index 00000000000000..3d130d395445cc --- /dev/null +++ b/src/controller/java/src/matter/controller/ResubscriptionAttemptCallback.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +fun interface ResubscriptionAttemptCallback { + fun onResubscriptionAttempt(terminationCause: Long, nextResubscribeIntervalMsec: Long) +} diff --git a/src/controller/java/src/matter/controller/SubscriptionEstablishedCallback.kt b/src/controller/java/src/matter/controller/SubscriptionEstablishedCallback.kt new file mode 100644 index 00000000000000..8cd5fdc2c07301 --- /dev/null +++ b/src/controller/java/src/matter/controller/SubscriptionEstablishedCallback.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +fun interface SubscriptionEstablishedCallback { + fun onSubscriptionEstablished(subscriptionId: Long) +} diff --git a/src/controller/java/src/matter/controller/WriteAttributesCallback.kt b/src/controller/java/src/matter/controller/WriteAttributesCallback.kt new file mode 100644 index 00000000000000..b4a3f112a02f83 --- /dev/null +++ b/src/controller/java/src/matter/controller/WriteAttributesCallback.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +import matter.controller.model.AttributePath + +/** An interface for receiving write response. */ +interface WriteAttributesCallback { + /** + * OnError will be called when an error occurs after failing to write + * + * @param attributePath The attribute path field + * @param Exception The IllegalStateException which encapsulated the error message, the possible + * chip error could be CHIP_ERROR_TIMEOUT: A response was not received within the expected + * response timeout. - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from + * the server. - CHIP_ERROR encapsulating the converted error from the StatusIB: If we got a + * non-path-specific status response from the server. CHIP_ERROR*: All other cases. + */ + fun onError(attributePath: AttributePath?, ex: Exception) + + /** + * OnResponse will be called when a write response has been received and processed for the given + * path. + * + * @param attributePath The attribute path field in write response. + */ + fun onResponse(attributePath: AttributePath) + + fun onDone() {} +} diff --git a/src/controller/java/src/matter/controller/WriteAttributesCallbackJni.kt b/src/controller/java/src/matter/controller/WriteAttributesCallbackJni.kt new file mode 100644 index 00000000000000..bd0bad3e228dda --- /dev/null +++ b/src/controller/java/src/matter/controller/WriteAttributesCallbackJni.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 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. + * + */ +package matter.controller + +import matter.controller.model.AttributePath + +/** JNI wrapper callback class for [WriteAttributesCallback]. */ +class WriteAttributesCallbackJni( + private val wrappedWriteAttributesCallback: WriteAttributesCallback +) { + private var callbackHandle: Long + + init { + this.callbackHandle = newCallback() + } + + private external fun newCallback(): Long + + private external fun deleteCallback(callbackHandle: Long) + + fun getCallbackHandle(): Long { + return callbackHandle + } + + // Called from native code only, which ignores access modifiers. + private fun onError( + isAttributePath: Boolean, + endpointId: Int, + clusterId: Long, + attributeId: Long, + e: Exception + ) { + wrappedWriteAttributesCallback.onError( + if (isAttributePath) + AttributePath(endpointId.toUShort(), clusterId.toUInt(), attributeId.toUInt()) + else null, + e + ) + } + + private fun onResponse(endpointId: Int, clusterId: Long, attributeId: Long) { + wrappedWriteAttributesCallback.onResponse( + AttributePath(endpointId.toUShort(), clusterId.toUInt(), attributeId.toUInt()) + ) + } + + private fun onDone() { + wrappedWriteAttributesCallback.onDone() + } + + // TODO(#8578): Replace finalizer with PhantomReference. + @Suppress("deprecation") + @Throws(Throwable::class) + protected fun finalize() { + if (callbackHandle != 0L) { + deleteCallback(callbackHandle) + callbackHandle = 0 + } + } +} diff --git a/src/controller/java/src/matter/controller/model/Paths.kt b/src/controller/java/src/matter/controller/model/Paths.kt index 76e26db62f0e1e..35fcd4e6bd411c 100644 --- a/src/controller/java/src/matter/controller/model/Paths.kt +++ b/src/controller/java/src/matter/controller/model/Paths.kt @@ -20,40 +20,74 @@ package matter.controller.model /** * Represents a full path for reading an attribute from a node. * - * @param endpointId The UShort representing the endpoint to read from. - * @param clusterId The UInt representing the cluster on the endpoint to read from. - * @param attributeId The UInt representing the attribute(s) on the cluster to read. + * @param endpointId The UShort representing the endpoint to read from. null is used to represent + * wildcards. + * @param clusterId The UInt representing the cluster on the endpoint to read from. null is used to + * represent wildcards. + * @param attributeId The UInt representing the attribute(s) on the cluster to read. null is used to + * represent wildcards. */ -data class AttributePath(val endpointId: UShort, val clusterId: UInt, val attributeId: UInt) { +data class AttributePath(val endpointId: UShort?, val clusterId: UInt?, val attributeId: UInt?) { override fun toString(): String = "$endpointId/$clusterId/$attributeId" + + private fun getEndpointId(wildcardId: Long): Long { + return endpointId?.toLong() ?: wildcardId + } + + private fun getClusterId(wildcardId: Long): Long { + return clusterId?.toLong() ?: wildcardId + } + + private fun getAttributeId(wildcardId: Long): Long { + return attributeId?.toLong() ?: wildcardId + } } /** * Represents a full path to an event emitted from a node. * - * @param endpointId The UShort representing the endpoint to read from. - * @param clusterId The UInt representing the cluster on the endpoint to read from. - * @param eventId The UInt representing the event(s) from the cluster. + * @param endpointId The UShort representing the endpoint to read from. null is used to represent + * wildcards. + * @param clusterId The UInt representing the cluster on the endpoint to read from. null is used to + * represent wildcards. + * @param eventId The UInt representing the event(s) from the cluster. null is used to represent + * wildcards. */ data class EventPath( - val endpointId: UShort, - val clusterId: UInt, - val eventId: UInt, + val endpointId: UShort?, + val clusterId: UInt?, + val eventId: UInt?, + val isUrgent: Boolean = false ) { override fun toString(): String = "$endpointId/$clusterId/$eventId" + + private fun getEndpointId(wildcardId: Long): Long { + return endpointId?.toLong() ?: wildcardId + } + + private fun getClusterId(wildcardId: Long): Long { + return clusterId?.toLong() ?: wildcardId + } + + private fun getEventId(wildcardId: Long): Long { + return eventId?.toLong() ?: wildcardId + } } /** * Represents a full path to a command sent to a node. * - * @param endpointId The UShort representing the endpoint to read from. + * @param endpointId The UShort representing the endpoint to read from. null is used to represent + * wildcards. * @param clusterId The UInt representing the cluster on the endpoint to read from. * @param commandId The UInt representing the command(s) from the cluster. + * @param groupId This UInt representing the group to read from. Used only in group path. */ data class CommandPath( - val endpointId: UShort, + val endpointId: UShort?, val clusterId: UInt, val commandId: UInt, + val groupId: UInt? = null ) { override fun toString(): String = "$endpointId/$clusterId/$commandId" } diff --git a/src/controller/java/src/matter/controller/model/States.kt b/src/controller/java/src/matter/controller/model/States.kt index 39899e7ce735f8..c913b37b9986f4 100644 --- a/src/controller/java/src/matter/controller/model/States.kt +++ b/src/controller/java/src/matter/controller/model/States.kt @@ -22,7 +22,150 @@ package matter.controller.model * * @param endpoints A mapping of endpoint IDs with the associated cluster data. */ -data class NodeState(val endpoints: Map) +class NodeState(val endpoints: MutableMap = mutableMapOf()) { + private fun addAttribute( + endpointId: UShort, + clusterId: UInt, + attributeId: UInt, + attributeState: AttributeState + ) { + getEndpointState(endpointId).addAttribute(clusterId, attributeId, attributeState) + } + + private fun addEvent(endpointId: UShort, clusterId: UInt, eventId: UInt, eventState: EventState) { + getEndpointState(endpointId).addEvent(clusterId, eventId, eventState) + } + + private fun setDataVersion(endpointId: UShort, clusterId: UInt, dataVersion: UInt) { + getEndpointState(endpointId).clusters[clusterId.toLong()]?.dataVersion = dataVersion + } + + private fun addAttributeStatus( + endpointId: UShort, + clusterId: UInt, + attributeId: UInt, + statusToAdd: Status + ) { + val endpointState = getEndpointState(endpointId) + val clusterState = endpointState.getClusterState(clusterId) + if (clusterState.attributes[attributeId.toLong()] != null) { + clusterState.attributes.remove(attributeId.toLong()) + } + clusterState.attributeStatuses[attributeId.toLong()] = statusToAdd + } + + private fun addEventStatus( + endpointId: UShort, + clusterId: UInt, + eventId: UInt, + statusToAdd: Status + ) { + val endpointState = getEndpointState(endpointId) + val clusterState = endpointState.getClusterState(clusterId) + if (clusterState.events[eventId.toLong()] != null) { + clusterState.events.remove(eventId.toLong()) + } + val eventStatuses = clusterState.eventStatuses.getOrDefault(eventId.toLong(), mutableListOf()) + eventStatuses.add(statusToAdd) + + clusterState.eventStatuses[eventId.toLong()] = eventStatuses + } + + private fun getEndpointState(endpointId: UShort): EndpointState { + var endpointState: EndpointState? = endpoints[endpointId.toInt()] + if (endpointState == null) { + endpointState = EndpointState(endpointId.toInt()) + endpoints[endpointId.toInt()] = endpointState + } + return endpointState + } + + private fun addAttribute( + endpointId: Int, + clusterId: Long, + attributeId: Long, + valueObject: Any, + tlv: ByteArray, + jsonString: String + ) { + addAttribute( + endpointId.toUShort(), + clusterId.toUInt(), + attributeId.toUInt(), + AttributeState( + attributeId, + tlv, + jsonString, + AttributePath(endpointId.toUShort(), clusterId.toUInt(), attributeId.toUInt()), + valueObject + ) + ) + } + + private fun addEvent( + endpointId: Int, + clusterId: Long, + eventId: Long, + eventNumber: Long, + priorityLevel: Int, + timestampType: Int, + timestampValue: Long, + valueObject: Any, + tlv: ByteArray, + jsonString: String + ) { + addEvent( + endpointId.toUShort(), + clusterId.toUInt(), + eventId.toUInt(), + EventState( + eventId, + eventNumber, + priorityLevel, + timestampType, + timestampValue, + tlv, + EventPath(endpointId.toUShort(), clusterId.toUInt(), eventId.toUInt()), + jsonString, + valueObject + ) + ) + } + + private fun setDataVersion(endpointId: Int, clusterId: Long, dataVersion: Long) { + setDataVersion(endpointId.toUShort(), clusterId.toUInt(), dataVersion.toUInt()) + } + + private fun addAttributeStatus( + endpointId: Int, + clusterId: Long, + attributeId: Long, + status: Int, + clusterStatus: Int? + ) { + addAttributeStatus( + endpointId.toUShort(), + clusterId.toUInt(), + attributeId.toUInt(), + Status(status, clusterStatus) + ) + } + + private fun addEventStatus( + endpointId: Int, + clusterId: Long, + eventId: Long, + status: Int, + clusterStatus: Int? + ) { + addEventStatus( + endpointId.toUShort(), + clusterId.toUInt(), + eventId.toUInt(), + Status(status, clusterStatus) + ) + } +} /** * Represents information about an endpoint and its cluster data. @@ -30,7 +173,24 @@ data class NodeState(val endpoints: Map) * @param id The endpoint ID. * @param clusters A mapping of cluster IDs to the cluster data. */ -data class EndpointState(val id: Int, val clusters: Map) +class EndpointState(val id: Int, val clusters: MutableMap = mutableMapOf()) { + fun addAttribute(clusterId: UInt, attributeId: UInt, attributeState: AttributeState) { + getClusterState(clusterId).addAttribute(attributeId, attributeState) + } + + fun addEvent(clusterId: UInt, eventId: UInt, eventState: EventState) { + getClusterState(clusterId).addEvent(eventId, eventState) + } + + internal fun getClusterState(clusterId: UInt): ClusterState { + var clusterState: ClusterState? = clusters[clusterId.toLong()] + if (clusterState == null) { + clusterState = ClusterState(clusterId.toLong()) + clusters[clusterId.toLong()] = clusterState + } + return clusterState + } +} /** * Represents information about a cluster. @@ -40,11 +200,28 @@ data class EndpointState(val id: Int, val clusters: Map) * @param events A mapping of event IDs to lists of events that occurred on the node under this * cluster. */ -data class ClusterState( +class ClusterState( val id: Long, - val attributes: Map, - val events: Map> -) + val attributes: MutableMap = mutableMapOf(), + val events: MutableMap> = mutableMapOf(), + var dataVersion: UInt? = null, + val attributeStatuses: MutableMap = mutableMapOf(), + val eventStatuses: MutableMap> = mutableMapOf() +) { + fun addAttribute(attributeId: UInt, attributeState: AttributeState) { + attributes[attributeId.toLong()] = attributeState + } + + fun addEvent(eventId: UInt, eventState: EventState) { + var eventStateList = events[eventId.toLong()] + if (eventStateList == null) { + eventStateList = mutableListOf() + events[eventId.toLong()] = eventStateList + } + + eventStateList.add(eventState) + } +} /** * Represents information about an attribute. @@ -53,7 +230,13 @@ data class ClusterState( * @param tlvValue The raw TLV-encoded attribute value. * @param jsonValue A JSON string representing the raw attribute value. */ -data class AttributeState(val id: Long, val tlvValue: ByteArray, val jsonValue: String) +data class AttributeState( + val id: Long, + val tlvValue: ByteArray, + val jsonValue: String, + val path: AttributePath, + val valueObject: Any? = null +) /** * Represents information about an event. @@ -71,5 +254,24 @@ data class EventState( val priorityLevel: Int, val timestampType: Int, val timestampValue: Long, - val tlvValue: ByteArray -) + val tlvValue: ByteArray, + val path: EventPath, + val jsonValue: String? = null, + val valueObject: Any? = null, +) { + enum class TypeStampTypeEnum(val type: Int) { + MILLIS_SINCE_BOOT(0), + MILLIS_SINCE_EPOCH(1) + } + + fun getTimestampType(): TypeStampTypeEnum? { + for (type in TypeStampTypeEnum.values()) { + if (type.type == timestampType) { + return type + } + } + return null + } +} + +data class Status(val status: Int, val clusterStatus: Int?) diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp index 506ae0a82b7788..c75a0ef83f222f 100644 --- a/src/lib/support/JniReferences.cpp +++ b/src/lib/support/JniReferences.cpp @@ -168,7 +168,12 @@ CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char * *methodId = env->GetMethodID(javaClass, methodName, method_signature.data()); env->ExceptionClear(); - VerifyOrReturnError(*methodId != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND); + if (*methodId == nullptr) + { + ChipLogError(Support, "methodId is null : %s, %s", methodName, methodSignature); + return CHIP_JNI_ERROR_METHOD_NOT_FOUND; + } + return CHIP_NO_ERROR; }