Skip to content

Commit

Permalink
Add JAVA/JNI Invoke command API (#25476)
Browse files Browse the repository at this point in the history
* Add Java/JNI Invoke command API

* address comments
  • Loading branch information
yunhanw-google authored and pull[bot] committed Feb 2, 2024
1 parent ff1124e commit 2215944
Show file tree
Hide file tree
Showing 13 changed files with 610 additions and 34 deletions.
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

0 comments on commit 2215944

Please sign in to comment.