Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JAVA/JNI Invoke command API #25476

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/java-matter-controller/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ java_library("java") {
"java/src/com/matter/controller/commands/pairing/PairOnNetworkFabricCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkInstanceNameCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkShortCommand.java",
"java/src/com/matter/controller/commands/pairing/PairOnNetworkVendorCommand.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private fun getImCommands(
): List<Command> {
return listOf(
PairOnNetworkLongImWriteCommand(controller, credentialsIssuer),
PairOnNetworkLongImInvokeCommand(controller, credentialsIssuer),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.matter.controller.commands.pairing;

import chip.devicecontroller.ChipDeviceController;
import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback;
import chip.devicecontroller.InvokeCallback;
import chip.devicecontroller.model.InvokeElement;
import com.matter.controller.commands.common.CredentialsIssuer;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class PairOnNetworkLongImInvokeCommand extends PairingCommand {
private static final Logger logger =
Logger.getLogger(PairOnNetworkLongImInvokeCommand.class.getName());
private static final int MATTER_PORT = 5540;
private static final int CLUSTER_ID_IDENTIFY = 0x0003;
private static final int IDENTIFY_COMMAND = 0;
private long devicePointer;

private void setDevicePointer(long devicePointer) {
this.devicePointer = devicePointer;
}

private class InternalInvokeCallback implements InvokeCallback {
@Override
public void onError(Exception e) {
logger.log(Level.INFO, "Invoke receive onError" + e.getMessage());
setFailure("write failure");
}

@Override
public void onResponse(InvokeElement element, long successCode) {
logger.log(Level.INFO, "Invoke receive OnResponse on ");
if (element != null) {
logger.log(Level.INFO, element.toString());
}
logger.log(Level.INFO, "success code is" + String.valueOf(successCode));
setSuccess();
}
}

private class InternalGetConnectedDeviceCallback implements GetConnectedDeviceCallback {
@Override
public void onDeviceConnected(long devicePointer) {
setDevicePointer(devicePointer);
logger.log(Level.INFO, "onDeviceConnected");
}

@Override
public void onConnectionFailure(long nodeId, Exception error) {
logger.log(Level.INFO, "onConnectionFailure");
}
}

public PairOnNetworkLongImInvokeCommand(
ChipDeviceController controller, CredentialsIssuer credsIssue) {
super(
controller,
"onnetwork-long-im-invoke",
PairingModeType.ON_NETWORK,
PairingNetworkType.NONE,
credsIssue,
DiscoveryFilterType.LONG_DISCRIMINATOR);
}

@Override
protected void runCommand() {
// tlv structure with tag 0, unsigned integer 1 inside, {0: 1}
byte[] intTLV = {0x15, 0x24, 0x00, 0x01, 0x18};
InvokeElement element =
InvokeElement.newInstance(
/* endpointId= */ 0, CLUSTER_ID_IDENTIFY, IDENTIFY_COMMAND, intTLV, null);

currentCommissioner()
.pairDeviceWithAddress(
getNodeId(),
getRemoteAddr().getHostAddress(),
MATTER_PORT,
getDiscriminator(),
getSetupPINCode(),
null);
currentCommissioner().setCompletionListener(this);
waitCompleteMs(getTimeoutMillis());
currentCommissioner()
.getConnectedDevicePointer(getNodeId(), new InternalGetConnectedDeviceCallback());
clear();

currentCommissioner().invoke(new InternalInvokeCallback(), devicePointer, element, 0, 0);

waitCompleteMs(getTimeoutMillis());
}
}
14 changes: 14 additions & 0 deletions src/controller/java/AndroidCallbacks-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,17 @@ JNI_METHOD(void, WriteAttributesCallbackJni, deleteCallback)(JNIEnv * env, jobje
VerifyOrReturn(writeAttributesCallback != nullptr, ChipLogError(Controller, "WriteAttributesCallback handle is nullptr"));
delete writeAttributesCallback;
}

JNI_METHOD(jlong, InvokeCallbackJni, newCallback)
(JNIEnv * env, jobject self, jobject invokeCallbackJava)
{
InvokeCallback * invokeCallback = chip::Platform::New<InvokeCallback>(self, invokeCallbackJava);
return reinterpret_cast<jlong>(invokeCallback);
}

JNI_METHOD(void, InvokeCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle)
{
InvokeCallback * invokeCallback = reinterpret_cast<InvokeCallback *>(callbackHandle);
VerifyOrReturn(invokeCallback != nullptr, ChipLogError(Controller, "InvokeCallback handle is nullptr"));
delete invokeCallback;
}
156 changes: 155 additions & 1 deletion src/controller/java/AndroidCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,61 @@ CHIP_ERROR CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath,
return err;
}

CHIP_ERROR InvokeCallback::CreateInvokeElement(const app::ConcreteCommandPath & aPath, TLV::TLVReader * apData, jobject & outObj)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
CHIP_ERROR err = CHIP_NO_ERROR;

jclass invokeElementCls = nullptr;
err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/model/InvokeElement", invokeElementCls);
ReturnErrorOnFailure(err);
JniClass invokeElementJniCls(invokeElementCls);

jmethodID invokeElementCtor = env->GetStaticMethodID(invokeElementCls, "newInstance",
"(JJJ[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);
readerForJson.Init(*apData);

// Create TLV byte array to pass to Java layer
size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead();
std::unique_ptr<uint8_t[]> buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufferLen]);
uint32_t size = 0;
// The TLVReader's read head is not pointing to the first element in the container, instead of the container itself, use
// a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with an anonymous tag, no extra "end of container" tag
// at the end.)
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<jbyte *>(buffer.get()), size);

// Convert TLV to JSON
Json::Value json;
err = TlvToJson(readerForJson, json);
ReturnErrorOnFailure(err);

UtfString jsonString(env, JsonToString(json).c_str());
outObj = env->CallStaticObjectMethod(invokeElementCls, invokeElementCtor, aPath.mEndpointId, aPath.mClusterId,
aPath.mCommandId, jniByteArray.jniValue(), jsonString.jniValue());
}
else
{
outObj = env->CallStaticObjectMethod(invokeElementCls, invokeElementCtor, aPath.mEndpointId, aPath.mClusterId,
aPath.mCommandId, nullptr, nullptr);
}
VerifyOrReturnError(outObj != nullptr, CHIP_JNI_ERROR_NULL_OBJECT);

return err;
}

CHIP_ERROR ReportCallback::CreateChipEventPath(const app::ConcreteEventPath & aPath, jobject & outObj)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
Expand Down Expand Up @@ -856,7 +911,7 @@ void WriteAttributesCallback::ReportError(jobject attributePath, const char * me
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();

ChipLogError(Controller, "ReportError is called");
ChipLogError(Controller, "WriteAttributesCallback ReportError is called");
jthrowable exception;
err = AndroidClusterExceptions::GetInstance().CreateIllegalStateException(env, message, errorCode, exception);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create IllegalStateException: %s", ErrorStr(err)));
Expand All @@ -872,5 +927,104 @@ void WriteAttributesCallback::ReportError(jobject attributePath, const char * me
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
}

InvokeCallback::InvokeCallback(jobject wrapperCallback, jobject javaCallback)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread"));

mWrapperCallbackRef = env->NewGlobalRef(wrapperCallback);
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
if (mWrapperCallbackRef == nullptr)
{
ChipLogError(Controller, "Could not create global reference for Wrapper InvokeCallback");
}
mJavaCallbackRef = env->NewGlobalRef(javaCallback);
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
if (mJavaCallbackRef == nullptr)
{
ChipLogError(Controller, "Could not create global reference for Java InvokeCallback");
}
}

InvokeCallback::~InvokeCallback()
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread"));
env->DeleteGlobalRef(mJavaCallbackRef);
if (mCommandSender != nullptr)
{
Platform::Delete(mCommandSender);
}
}

void InvokeCallback::OnResponse(app::CommandSender * apCommandSender, const app::ConcreteCommandPath & aPath,
const app::StatusIB & aStatusIB, TLV::TLVReader * apData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
jobject invokeElementObj = nullptr;
jmethodID onResponseMethod;

err = CreateInvokeElement(aPath, apData, invokeElementObj);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create Java InvokeElement: %s", ErrorStr(err)));
err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "onResponse",
"(Lchip/devicecontroller/model/InvokeElement;J)V", &onResponseMethod);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err)));

DeviceLayer::StackUnlock unlock;
env->CallVoidMethod(mJavaCallbackRef, onResponseMethod, invokeElementObj,
static_cast<std::underlying_type_t<Protocols::InteractionModel::Status>>(aStatusIB.mStatus));
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
}

void InvokeCallback::OnError(const app::CommandSender * apCommandSender, CHIP_ERROR aError)
{
ReportError(aError);
}

void InvokeCallback::OnDone(app::CommandSender * apCommandSender)
{
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();

jmethodID onDoneMethod;
err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "onDone", "()V", &onDoneMethod);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onDone method"));

DeviceLayer::StackUnlock unlock;
env->CallVoidMethod(mJavaCallbackRef, onDoneMethod);
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
JniReferences::GetInstance().GetEnvForCurrentThread()->DeleteGlobalRef(mWrapperCallbackRef);
}

void InvokeCallback::ReportError(CHIP_ERROR err)
{
ReportError(ErrorStr(err), err.AsInteger());
}

void InvokeCallback::ReportError(Protocols::InteractionModel::Status status)
{
ReportError("IM Status", static_cast<std::underlying_type_t<Protocols::InteractionModel::Status>>(status));
}

void InvokeCallback::ReportError(const char * message, ChipError::StorageType errorCode)
{
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();

ChipLogError(Controller, "InvokeCallback ReportError is called");
jthrowable exception;
err = AndroidClusterExceptions::GetInstance().CreateIllegalStateException(env, message, errorCode, exception);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create IllegalStateException: %s", ErrorStr(err)));

jmethodID onErrorMethod;
err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "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(mJavaCallbackRef, onErrorMethod, exception);
VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
}

} // namespace Controller
} // namespace chip
23 changes: 23 additions & 0 deletions src/controller/java/AndroidCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma once

#include <app/BufferedReadCallback.h>
#include <app/CommandSender.h>
#include <app/ReadClient.h>
#include <app/WriteClient.h>
#include <controller/CHIPDeviceController.h>
Expand Down Expand Up @@ -152,5 +153,27 @@ struct WriteAttributesCallback : public app::WriteClient::Callback
jobject mJavaCallbackRef = nullptr;
};

struct InvokeCallback : public app::CommandSender::Callback
{
InvokeCallback(jobject wrapperCallback, jobject javaCallback);
~InvokeCallback();

void OnResponse(app::CommandSender * apCommandSender, const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatusIB,
TLV::TLVReader * apData) override;
/** Report errors back to Java layer. attributePath may be nullptr for general errors. */
void OnError(const app::CommandSender * apCommandSender, CHIP_ERROR aError) override;

void OnDone(app::CommandSender * apCommandSender) override;

CHIP_ERROR CreateInvokeElement(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;
jobject mWrapperCallbackRef = nullptr;
jobject mJavaCallbackRef = nullptr;
};

} // namespace Controller
} // namespace chip
2 changes: 2 additions & 0 deletions src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ android_library("java") {
"src/chip/devicecontroller/DeviceAttestationDelegate.java",
"src/chip/devicecontroller/DiscoveredDevice.java",
"src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java",
"src/chip/devicecontroller/InvokeCallback.java",
"src/chip/devicecontroller/InvokeCallbackJni.java",
"src/chip/devicecontroller/KeypairDelegate.java",
"src/chip/devicecontroller/NetworkCredentials.java",
"src/chip/devicecontroller/NetworkLocation.java",
Expand Down
Loading