[Android] Add Status handling for onReport code path (#32082)
diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
index 3e45d62..e0a95d5 100644
--- a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
+++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
@@ -10,6 +10,7 @@
import chip.devicecontroller.model.DataVersionFilter
import chip.devicecontroller.model.EventState
import chip.devicecontroller.model.NodeState
+import chip.devicecontroller.model.Status
import com.matter.controller.commands.common.CredentialsIssuer
import java.util.logging.Level
import java.util.logging.Logger
@@ -34,13 +35,6 @@
eventPath: ChipEventPath?,
e: Exception
) {
- if (attributePath != null && attributePath.clusterId.getId() == UNIT_TEST_CLUSTER) {
- logger.log(
- Level.INFO,
- "TODO: skip the error check for unit test cluster that covers most error result"
- )
- return
- }
logger.log(Level.INFO, "Read receive onError")
setFailure("read failure")
}
@@ -72,11 +66,20 @@
return cluster.equals(expected)
}
+ fun checkUnitTestClusterGeneralStatus(status: Status): Boolean =
+ (status.getStatus() == CLUSTER_ID_TEST_GENERAL_ERROR_STATUS) &&
+ !status.getClusterStatus().isPresent()
+
+ fun checkUnitTestClusterClusterStatus(status: Status): Boolean =
+ (status.getStatus() == CLUSTER_ID_TEST_CLUSTER_ERROR_STATUS) &&
+ status.getClusterStatus().isPresent() &&
+ status.getClusterStatus().get() == CLUSTER_ID_TEST_CLUSTER_ERROR_CLUSTER_STATUS
+
private fun validateResponse(nodeState: NodeState) {
val endpointZero =
requireNotNull(nodeState.getEndpointState(0)) { "Endpoint zero not found." }
- val endpointOne = requireNotNull(nodeState.getEndpointState(0)) { "Endpoint one not found." }
+ val endpointOne = requireNotNull(nodeState.getEndpointState(1)) { "Endpoint one not found." }
val basicCluster =
requireNotNull(endpointZero.getClusterState(CLUSTER_ID_BASIC)) {
@@ -93,6 +96,11 @@
"No local config disabled attribute found."
}
+ val unitTestCluster =
+ requireNotNull(endpointOne.getClusterState(UNIT_TEST_CLUSTER)) {
+ "Unit test cluster not found."
+ }
+
val startUpEvents =
requireNotNull(basicCluster.getEventState(EVENT_ID_START_UP)) { "No start up event found." }
@@ -122,6 +130,22 @@
require(checkAllAttributesJsonForFixedLabel(clusterAttributes)) {
"Invalid fixed label cluster attributes Json ${clusterAttributes}"
}
+
+ require(
+ checkUnitTestClusterGeneralStatus(
+ unitTestCluster.getAttributeStatuses()[CLUSTER_ID_TEST_GENERAL_ERROR_BOOLEAN]!!
+ )
+ ) {
+ "Invalid unit test cluster generalStatus check ${unitTestCluster}"
+ }
+
+ require(
+ checkUnitTestClusterClusterStatus(
+ unitTestCluster.getAttributeStatuses()[CLUSTER_ID_TEST_CLUSTER_ERROR_BOOLEAN]!!
+ )
+ ) {
+ "Invalid unit test cluster clusterStatus check ${unitTestCluster}"
+ }
}
override fun onReport(nodeState: NodeState) {
@@ -212,10 +236,15 @@
private const val MATTER_PORT = 5540
private const val CLUSTER_ID_BASIC = 0x0028L
private const val FIXED_LABEL_CLUSTER = 0x0040L
- private const val UNIT_TEST_CLUSTER = 0xfff1fc05
+ private const val UNIT_TEST_CLUSTER = 0xfff1fc05L
private const val ATTR_ID_LOCAL_CONFIG_DISABLED = 16L
private const val EVENT_ID_START_UP = 0L
private const val GLOBAL_ATTRIBUTE_LIST = 65531L
private const val CLUSTER_ID_BASIC_VERSION = 0L
+ private const val CLUSTER_ID_TEST_GENERAL_ERROR_BOOLEAN = 0x0031L
+ private const val CLUSTER_ID_TEST_CLUSTER_ERROR_BOOLEAN = 0x0032L
+ private const val CLUSTER_ID_TEST_GENERAL_ERROR_STATUS = 0x8d
+ private const val CLUSTER_ID_TEST_CLUSTER_ERROR_STATUS = 1
+ private const val CLUSTER_ID_TEST_CLUSTER_ERROR_CLUSTER_STATUS = 17
}
}
diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp
index 88bbf98..9e3e3e9 100644
--- a/src/controller/java/AndroidCallbacks.cpp
+++ b/src/controller/java/AndroidCallbacks.cpp
@@ -55,22 +55,6 @@
return CHIP_NO_ERROR;
}
-CHIP_ERROR ReportCallback::CreateChipEventPath(JNIEnv * env, const app::ConcreteEventPath & aPath, jobject & outObj)
-{
- jclass eventPathCls = nullptr;
- ReturnErrorOnFailure(
- JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/model/ChipEventPath", eventPathCls));
-
- jmethodID eventPathCtor =
- env->GetStaticMethodID(eventPathCls, "newInstance", "(IJJ)Lchip/devicecontroller/model/ChipEventPath;");
- VerifyOrReturnError(eventPathCtor != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND);
-
- outObj = env->CallStaticObjectMethod(eventPathCls, eventPathCtor, static_cast<jint>(aPath.mEndpointId),
- static_cast<jlong>(aPath.mClusterId), static_cast<jlong>(aPath.mEventId));
- VerifyOrReturnError(outObj != nullptr, CHIP_JNI_ERROR_NULL_OBJECT);
- return CHIP_NO_ERROR;
-}
-
GetConnectedDeviceCallback::GetConnectedDeviceCallback(jobject wrapperCallback, jobject javaCallback) :
mOnSuccess(OnDeviceConnectedFn, this), mOnFailure(OnDeviceConnectionFailureFn, this)
{
@@ -270,6 +254,30 @@
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<jint>(aStatus.mStatus),
+ static_cast<jint>(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<jint>(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)
{
@@ -277,14 +285,28 @@
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
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)));
-
VerifyOrReturn(!aPath.IsListItemOperation(), ChipLogError(Controller, "Expect non-list item operation"); aPath.LogPath());
- VerifyOrReturn(aStatus.IsSuccess(), ChipLogError(Controller, "Receive bad status %s", ErrorStr(aStatus.ToChipError()));
- aPath.LogPath());
+
+ jobject nodeState = mNodeStateObj.ObjectRef();
+ 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
+ jmethodID addAttributeStatusMethod = nullptr;
+ err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttributeStatus",
+ "(IJJLchip/devicecontroller/model/Status;)V", &addAttributeStatusMethod);
+ VerifyOrReturn(
+ err == CHIP_NO_ERROR,
+ ChipLogError(Controller, "Could not find addAttributeStatus method with error %" CHIP_ERROR_FORMAT, err.Format()));
+ env->CallVoidMethod(nodeState, addAttributeStatusMethod, static_cast<jint>(aPath.mEndpointId),
+ static_cast<jlong>(aPath.mClusterId), static_cast<jlong>(aPath.mAttributeId), statusObj);
+ VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
+ return;
+ }
VerifyOrReturn(apData != nullptr, ChipLogError(Controller, "Receive empty apData"); aPath.LogPath());
TLV::TLVReader readerForJavaTLV;
@@ -345,7 +367,6 @@
VerifyOrReturn(attributeStateObj != nullptr, ChipLogError(Controller, "Could not create AttributeState object");
aPath.LogPath());
- jobject nodeState = mNodeStateObj.ObjectRef();
// Add AttributeState to NodeState
jmethodID addAttributeMethod;
err = JniReferences::GetInstance().FindMethod(env, nodeState, "addAttribute",
@@ -401,10 +422,28 @@
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread"));
- jobject eventPathObj = nullptr;
- err = CreateChipEventPath(env, aEventHeader.mPath, eventPathObj);
- VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create Java ChipEventPath: %s", ErrorStr(err)));
+ jobject nodeState = mNodeStateObj.ObjectRef();
+ 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);
+ VerifyOrReturn(
+ err == CHIP_NO_ERROR,
+ ChipLogError(Controller, "Could not find addEventStatus method with error %" CHIP_ERROR_FORMAT, err.Format()));
+ env->CallVoidMethod(nodeState, addEventStatusMethod, static_cast<jint>(aEventHeader.mPath.mEndpointId),
+ static_cast<jlong>(aEventHeader.mPath.mClusterId), static_cast<jlong>(aEventHeader.mPath.mEventId),
+ statusObj);
+ VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe());
+ return;
+ }
VerifyOrReturn(apData != nullptr, ChipLogError(Controller, "Receive empty apData"); aEventHeader.LogPath());
TLV::TLVReader readerForJavaTLV;
@@ -484,7 +523,7 @@
// Add EventState to NodeState
jmethodID addEventMethod;
- jobject nodeState = mNodeStateObj.ObjectRef();
+
err = JniReferences::GetInstance().FindMethod(env, nodeState, "addEvent", "(IJJLchip/devicecontroller/model/EventState;)V",
&addEventMethod);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addEvent method with error %s", ErrorStr(err));
diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn
index 0f68fce..0e5beda 100644
--- a/src/controller/java/BUILD.gn
+++ b/src/controller/java/BUILD.gn
@@ -482,6 +482,7 @@
"src/chip/devicecontroller/model/EventState.java",
"src/chip/devicecontroller/model/InvokeElement.java",
"src/chip/devicecontroller/model/NodeState.java",
+ "src/chip/devicecontroller/model/Status.java",
]
if (matter_enable_tlv_decoder_api) {
diff --git a/src/controller/java/src/chip/devicecontroller/model/ClusterState.java b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
index 4c575f1..6ebe40e 100644
--- a/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
+++ b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java
@@ -32,12 +32,19 @@
private static final String TAG = "ClusterState";
private Map<Long, AttributeState> attributes;
private Map<Long, ArrayList<EventState>> events;
+ private Map<Long, Status> attributeStatuses;
+ private Map<Long, ArrayList<Status>> eventStatuses;
private Optional<Long> dataVersion;
- public ClusterState(
- Map<Long, AttributeState> attributes, Map<Long, ArrayList<EventState>> events) {
+ protected ClusterState(
+ Map<Long, AttributeState> attributes,
+ Map<Long, ArrayList<EventState>> events,
+ Map<Long, Status> attributeStatuses,
+ Map<Long, ArrayList<Status>> eventStatuses) {
this.attributes = attributes;
this.events = events;
+ this.attributeStatuses = attributeStatuses;
+ this.eventStatuses = eventStatuses;
this.dataVersion = Optional.empty();
}
@@ -45,10 +52,18 @@
return attributes;
}
+ public Map<Long, Status> getAttributeStatuses() {
+ return attributeStatuses;
+ }
+
public Map<Long, ArrayList<EventState>> getEventStates() {
return events;
}
+ public Map<Long, ArrayList<Status>> getEventStatuses() {
+ return eventStatuses;
+ }
+
public void setDataVersion(long version) {
dataVersion = Optional.of(version);
}
@@ -130,6 +145,25 @@
builder.append("\n");
});
});
+ attributeStatuses.forEach(
+ (attributeId, status) -> {
+ builder.append("Attribute Status ");
+ builder.append(attributeId);
+ builder.append(": ");
+ builder.append(status.toString());
+ builder.append("\n");
+ });
+ eventStatuses.forEach(
+ (eventId, status) -> {
+ status.forEach(
+ (eventState) -> {
+ builder.append("Event Status");
+ builder.append(eventId);
+ builder.append(": ");
+ builder.append(status.toString());
+ builder.append("\n");
+ });
+ });
return builder.toString();
}
}
diff --git a/src/controller/java/src/chip/devicecontroller/model/NodeState.java b/src/controller/java/src/chip/devicecontroller/model/NodeState.java
index decd978..f77097b 100644
--- a/src/controller/java/src/chip/devicecontroller/model/NodeState.java
+++ b/src/controller/java/src/chip/devicecontroller/model/NodeState.java
@@ -55,10 +55,15 @@
ClusterState clusterState = endpointState.getClusterState(clusterId);
if (clusterState == null) {
- clusterState = new ClusterState(new HashMap<>(), new HashMap<>());
+ clusterState =
+ new ClusterState(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
endpointState.getClusterStates().put(clusterId, clusterState);
}
+ if (clusterState.getAttributeStatuses().containsKey(attributeId)) {
+ clusterState.getAttributeStatuses().remove(attributeId);
+ }
+
// This will overwrite previous attributes.
clusterState.getAttributeStates().put(attributeId, attributeStateToAdd);
}
@@ -72,16 +77,70 @@
ClusterState clusterState = endpointState.getClusterState(clusterId);
if (clusterState == null) {
- clusterState = new ClusterState(new HashMap<>(), new HashMap<>());
+ clusterState =
+ new ClusterState(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
endpointState.getClusterStates().put(clusterId, clusterState);
}
if (!clusterState.getEventStates().containsKey(eventId)) {
clusterState.getEventStates().put(eventId, new ArrayList<EventState>());
}
+
+ if (clusterState.getEventStatuses().containsKey(eventId)) {
+ clusterState.getEventStatuses().remove(eventId);
+ }
+
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);
+ if (endpointState == null) {
+ endpointState = new EndpointState(new HashMap<>());
+ getEndpointStates().put(endpointId, endpointState);
+ }
+
+ ClusterState clusterState = endpointState.getClusterState(clusterId);
+ if (clusterState == null) {
+ clusterState =
+ new ClusterState(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
+ endpointState.getClusterStates().put(clusterId, clusterState);
+ }
+
+ if (clusterState.getAttributeStates().containsKey(attributeId)) {
+ clusterState.getAttributeStates().remove(attributeId);
+ }
+
+ clusterState.getAttributeStatuses().put(attributeId, statusToAdd);
+ }
+
+ private void addEventStatus(int endpointId, long clusterId, long eventId, Status statusToAdd) {
+ EndpointState endpointState = getEndpointState(endpointId);
+ if (endpointState == null) {
+ endpointState = new EndpointState(new HashMap<>());
+ getEndpointStates().put(endpointId, endpointState);
+ }
+
+ ClusterState clusterState = endpointState.getClusterState(clusterId);
+ if (clusterState == null) {
+ clusterState =
+ new ClusterState(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
+ endpointState.getClusterStates().put(clusterId, clusterState);
+ }
+
+ if (!clusterState.getEventStatuses().containsKey(eventId)) {
+ clusterState.getEventStatuses().put(eventId, new ArrayList<Status>());
+ }
+
+ if (clusterState.getEventStates().containsKey(eventId)) {
+ clusterState.getEventStates().remove(eventId);
+ }
+
+ clusterState.getEventStatuses().get(eventId).add(statusToAdd);
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/controller/java/src/chip/devicecontroller/model/Status.java b/src/controller/java/src/chip/devicecontroller/model/Status.java
new file mode 100644
index 0000000..7391f9a
--- /dev/null
+++ b/src/controller/java/src/chip/devicecontroller/model/Status.java
@@ -0,0 +1,57 @@
+/*
+ * 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 chip.devicecontroller.model;
+
+import java.util.Locale;
+import java.util.Optional;
+
+public final class Status {
+ private Integer status;
+ private Optional<Integer> clusterStatus;
+
+ private Status(int status, Optional<Integer> clusterStatus) {
+ this.status = status;
+ this.clusterStatus = clusterStatus;
+ }
+
+ // Getters
+ public Integer getStatus() {
+ return status;
+ }
+
+ public Optional<Integer> getClusterStatus() {
+ return clusterStatus;
+ }
+
+ public String toString() {
+ return String.format(
+ Locale.ENGLISH,
+ "status %s, clusterStatus %s",
+ String.valueOf(status),
+ clusterStatus.isPresent() ? String.valueOf(clusterStatus.get()) : "None");
+ }
+
+ public static Status newInstance(int status, int clusterStatus) {
+ return new Status(status, Optional.of(clusterStatus));
+ }
+
+ public static Status newInstance(int status) {
+ return new Status(status, Optional.empty());
+ }
+}