Messages cluster sample app for android (#32162)
* sample app for java
* present and list messages
* address feedback
* Restyle [in-dev] Messages cluster sample app for android (#32163)
* Restyled by whitespace
* Restyled by google-java-format
---------
Co-authored-by: Restyled.io <commits@restyled.io>
* Restyled by google-java-format (#32195)
Co-authored-by: Restyled.io <commits@restyled.io>
* address feedback
---------
Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
index 79b02f0..540798a 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
@@ -41,6 +41,7 @@
import com.matter.tv.server.tvapp.LowPowerManagerStub;
import com.matter.tv.server.tvapp.MediaInputManagerStub;
import com.matter.tv.server.tvapp.MediaPlaybackManagerStub;
+import com.matter.tv.server.tvapp.MessagesManagerStub;
import com.matter.tv.server.tvapp.OnOffManagerStub;
import com.matter.tv.server.tvapp.TvApp;
import com.matter.tv.server.tvapp.WakeOnLanManagerStub;
@@ -96,6 +97,8 @@
app.setMediaPlaybackManager(endpoint, new MediaPlaybackManagerStub(endpoint));
} else if (clusterId == Clusters.ClusterId_Channel) {
app.setChannelManager(endpoint, new ChannelManagerStub(endpoint));
+ } else if (clusterId == Clusters.ClusterId_Messaging) {
+ app.setMessagesManager(endpoint, new MessagesManagerStub(endpoint));
} else if (clusterId == Clusters.ClusterId_OnOff) {
mOnOffEndpoint = endpoint;
app.setOnOffManager(endpoint, new OnOffManagerStub(endpoint));
diff --git a/examples/tv-app/android/BUILD.gn b/examples/tv-app/android/BUILD.gn
index 63de5a5..47157a2 100644
--- a/examples/tv-app/android/BUILD.gn
+++ b/examples/tv-app/android/BUILD.gn
@@ -69,6 +69,8 @@
"java/MediaInputManager.h",
"java/MediaPlaybackManager.cpp",
"java/MediaPlaybackManager.h",
+ "java/MessagesManager.cpp",
+ "java/MessagesManager.h",
"java/MyUserPrompter-JNI.cpp",
"java/MyUserPrompter-JNI.h",
"java/MyUserPrompterResolver-JNI.cpp",
@@ -143,6 +145,10 @@
"java/src/com/matter/tv/server/tvapp/MediaPlaybackManagerStub.java",
"java/src/com/matter/tv/server/tvapp/MediaPlaybackPosition.java",
"java/src/com/matter/tv/server/tvapp/MediaTrack.java",
+ "java/src/com/matter/tv/server/tvapp/Message.java",
+ "java/src/com/matter/tv/server/tvapp/MessageResponseOption.java",
+ "java/src/com/matter/tv/server/tvapp/MessagesManager.java",
+ "java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java",
"java/src/com/matter/tv/server/tvapp/OnOffManager.java",
"java/src/com/matter/tv/server/tvapp/OnOffManagerStub.java",
"java/src/com/matter/tv/server/tvapp/TvApp.java",
diff --git a/examples/tv-app/android/java/ChannelManager.cpp b/examples/tv-app/android/java/ChannelManager.cpp
index 3c0efca..c706713 100644
--- a/examples/tv-app/android/java/ChannelManager.cpp
+++ b/examples/tv-app/android/java/ChannelManager.cpp
@@ -57,6 +57,7 @@
CHIP_ERROR ChannelManager::HandleGetChannelList(AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -66,6 +67,8 @@
VerifyOrExit(mChannelManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetChannelListMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
jobjectArray channelInfoList =
(jobjectArray) env->CallObjectMethod(mChannelManagerObject.ObjectRef(), mGetChannelListMethod);
@@ -134,6 +137,7 @@
CHIP_ERROR ChannelManager::HandleGetLineup(AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
chip::app::Clusters::Channel::Structs::LineupInfoStruct::Type lineupInfo;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -144,6 +148,8 @@
VerifyOrExit(mChannelManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetLineupMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
{
jobject channelLineupObject = env->CallObjectMethod(mChannelManagerObject.ObjectRef(), mGetLineupMethod);
if (channelLineupObject != nullptr)
@@ -197,6 +203,7 @@
CHIP_ERROR ChannelManager::HandleGetCurrentChannel(AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
chip::app::Clusters::Channel::Structs::ChannelInfoStruct::Type channelInfo;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -207,6 +214,8 @@
VerifyOrExit(mChannelManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetCurrentChannelMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
{
jobject channelInfoObject = env->CallObjectMethod(mChannelManagerObject.ObjectRef(), mGetCurrentChannelMethod);
if (channelInfoObject != nullptr)
@@ -273,6 +282,7 @@
void ChannelManager::HandleChangeChannel(CommandResponseHelper<ChangeChannelResponseType> & helper, const CharSpan & match)
{
+ DeviceLayer::StackUnlock unlock;
std::string name(match.data(), match.size());
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -282,9 +292,10 @@
VerifyOrExit(mChannelManagerObject.HasValidObjectRef(), ChipLogError(Zcl, "mChannelManagerObject null"));
VerifyOrExit(mChangeChannelMethod != nullptr, ChipLogError(Zcl, "mChangeChannelMethod null"));
+ env->ExceptionClear();
+
{
UtfString jniname(env, name.c_str());
- env->ExceptionClear();
jobject channelObject = env->CallObjectMethod(mChannelManagerObject.ObjectRef(), mChangeChannelMethod, jniname.jniValue());
if (env->ExceptionCheck())
{
@@ -319,6 +330,7 @@
bool ChannelManager::HandleChangeChannelByNumber(const uint16_t & majorNumber, const uint16_t & minorNumber)
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -347,6 +359,7 @@
bool ChannelManager::HandleSkipChannel(const int16_t & count)
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -379,6 +392,7 @@
const chip::Optional<chip::app::DataModel::DecodableList<AdditionalInfoType>> & externalIdList,
const chip::Optional<chip::ByteSpan> & data)
{
+ DeviceLayer::StackUnlock unlock;
ProgramGuideResponseType response;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -394,6 +408,8 @@
VerifyOrExit(mChannelManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetProgramGuideMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
{
// NOTE: this example app does not pass the Data, PageToken, ChannelsArray or ExternalIdList through to the Java layer
UtfString jData(env, "");
@@ -591,6 +607,7 @@
const DataModel::DecodableList<AdditionalInfo> & externalIdList,
const chip::ByteSpan & data)
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -628,6 +645,7 @@
const DataModel::DecodableList<AdditionalInfo> & externalIdList,
const chip::ByteSpan & data)
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
diff --git a/examples/tv-app/android/java/ContentAppAttributeDelegate.cpp b/examples/tv-app/android/java/ContentAppAttributeDelegate.cpp
index 84e7d99..00b4ca7 100644
--- a/examples/tv-app/android/java/ContentAppAttributeDelegate.cpp
+++ b/examples/tv-app/android/java/ContentAppAttributeDelegate.cpp
@@ -28,6 +28,7 @@
#include <lib/support/CHIPJNIError.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
+#include <platform/PlatformManager.h>
#include <zap-generated/endpoint_config.h>
namespace chip {
@@ -43,6 +44,7 @@
return "";
}
+ DeviceLayer::StackUnlock unlock;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
ChipLogProgress(Zcl, "ContentAppAttributeDelegate::Read being called for endpoint %d cluster %d attribute %d",
aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId);
diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
index 3963140..2e5dcbe 100644
--- a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
+++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
@@ -31,6 +31,7 @@
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <lib/support/jsontlv/TlvJson.h>
+#include <platform/PlatformManager.h>
#include <zap-generated/endpoint_config.h>
namespace chip {
@@ -50,6 +51,7 @@
{
if (handlerContext.mRequestPath.mEndpointId >= FIXED_ENDPOINT_COUNT)
{
+ DeviceLayer::StackUnlock unlock;
TLV::TLVReader readerForJson;
readerForJson.Init(handlerContext.mPayload);
diff --git a/examples/tv-app/android/java/ContentLauncherManager.cpp b/examples/tv-app/android/java/ContentLauncherManager.cpp
index caa14b0..f68e113 100644
--- a/examples/tv-app/android/java/ContentLauncherManager.cpp
+++ b/examples/tv-app/android/java/ContentLauncherManager.cpp
@@ -51,6 +51,7 @@
const chip::Optional<PlaybackPreferencesType> playbackPreferences,
bool useCurrentContext)
{
+ DeviceLayer::StackUnlock unlock;
Commands::LauncherResponse::Type response;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -61,6 +62,7 @@
VerifyOrExit(mContentLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mLaunchContentMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
{
UtfString jData(env, data);
@@ -106,6 +108,7 @@
const chip::CharSpan & displayString,
const BrandingInformationType & brandingInformation)
{
+ DeviceLayer::StackUnlock unlock;
Commands::LauncherResponse::Type response;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -116,6 +119,8 @@
VerifyOrExit(mContentLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mLaunchUrlMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
{
UtfString jContentUrl(env, contentUrl);
UtfString jDisplayString(env, displayString);
@@ -160,6 +165,7 @@
CHIP_ERROR ContentLauncherManager::HandleGetAcceptHeaderList(AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
std::list<std::string> acceptedHeadersList;
@@ -170,6 +176,8 @@
VerifyOrExit(mContentLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetAcceptHeaderMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
jobjectArray acceptedHeadersArray =
(jobjectArray) env->CallObjectMethod(mContentLauncherManagerObject.ObjectRef(), mGetAcceptHeaderMethod);
@@ -203,6 +211,7 @@
uint32_t ContentLauncherManager::HandleGetSupportedStreamingProtocols()
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
uint32_t supportedStreamingProtocols = 0;
@@ -213,6 +222,8 @@
VerifyOrExit(mContentLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetSupportedStreamingProtocolsMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
{
jlong jSupportedStreamingProtocols =
env->CallLongMethod(mContentLauncherManagerObject.ObjectRef(), mGetSupportedStreamingProtocolsMethod);
diff --git a/examples/tv-app/android/java/KeypadInputManager.cpp b/examples/tv-app/android/java/KeypadInputManager.cpp
index a43a56c..2382397 100644
--- a/examples/tv-app/android/java/KeypadInputManager.cpp
+++ b/examples/tv-app/android/java/KeypadInputManager.cpp
@@ -44,6 +44,7 @@
void KeypadInputManager::HandleSendKey(CommandResponseHelper<SendKeyResponseType> & helper, const CECKeyCodeEnum & keyCode)
{
+ DeviceLayer::StackUnlock unlock;
Commands::SendKeyResponse::Type response;
jint ret = -1;
diff --git a/examples/tv-app/android/java/LevelManager.cpp b/examples/tv-app/android/java/LevelManager.cpp
index b79f150..f9be757 100644
--- a/examples/tv-app/android/java/LevelManager.cpp
+++ b/examples/tv-app/android/java/LevelManager.cpp
@@ -112,6 +112,7 @@
void LevelManager::HandleLevelChanged(uint8_t value)
{
+ DeviceLayer::StackUnlock unlock;
ChipLogProgress(Zcl, "LevelManager::HandleLevelChanged");
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
diff --git a/examples/tv-app/android/java/LowPowerManager.cpp b/examples/tv-app/android/java/LowPowerManager.cpp
index 12e2349..5c6906f 100644
--- a/examples/tv-app/android/java/LowPowerManager.cpp
+++ b/examples/tv-app/android/java/LowPowerManager.cpp
@@ -64,6 +64,7 @@
bool LowPowerManager::HandleSleep()
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
JniLocalReferenceScope scope(env);
diff --git a/examples/tv-app/android/java/MediaInputManager.cpp b/examples/tv-app/android/java/MediaInputManager.cpp
index bbc575b..0f1c922 100644
--- a/examples/tv-app/android/java/MediaInputManager.cpp
+++ b/examples/tv-app/android/java/MediaInputManager.cpp
@@ -51,6 +51,7 @@
CHIP_ERROR MediaInputManager::HandleGetInputList(chip::app::AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -60,6 +61,8 @@
VerifyOrExit(mMediaInputManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetInputListMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
jobjectArray inputArray = (jobjectArray) env->CallObjectMethod(mMediaInputManagerObject.ObjectRef(), mGetInputListMethod);
if (env->ExceptionCheck())
@@ -121,6 +124,7 @@
uint8_t MediaInputManager::HandleGetCurrentInput()
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
jint index = -1;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -130,7 +134,8 @@
ChipLogProgress(Zcl, "Received MediaInputManager::HandleGetCurrentInput");
VerifyOrExit(mMediaInputManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetCurrentInputMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
- VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV);
+
+ env->ExceptionClear();
{
index = env->CallIntMethod(mMediaInputManagerObject.ObjectRef(), mGetCurrentInputMethod);
@@ -154,6 +159,7 @@
bool MediaInputManager::HandleSelectInput(const uint8_t index)
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -179,6 +185,7 @@
bool MediaInputManager::HandleShowInputStatus()
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -204,6 +211,7 @@
bool MediaInputManager::HandleHideInputStatus()
{
+ DeviceLayer::StackUnlock unlock;
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnValue(env != nullptr, false, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -229,6 +237,7 @@
bool MediaInputManager::HandleRenameInput(const uint8_t index, const chip::CharSpan & name)
{
+ DeviceLayer::StackUnlock unlock;
std::string inputname(name.data(), name.size());
jboolean ret = JNI_FALSE;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -239,9 +248,10 @@
VerifyOrExit(mMediaInputManagerObject.HasValidObjectRef(), ChipLogError(Zcl, "mMediaInputManagerObject is not valid"));
VerifyOrExit(mRenameInputMethod != nullptr, ChipLogError(Zcl, "mHideInputStatusMethod null"));
+ env->ExceptionClear();
+
{
UtfString jniInputname(env, inputname.data());
- env->ExceptionClear();
ret = env->CallBooleanMethod(mMediaInputManagerObject.ObjectRef(), mRenameInputMethod, static_cast<jint>(index),
jniInputname.jniValue());
if (env->ExceptionCheck())
diff --git a/examples/tv-app/android/java/MediaPlaybackManager.cpp b/examples/tv-app/android/java/MediaPlaybackManager.cpp
index 62abeb1..667ab2e 100644
--- a/examples/tv-app/android/java/MediaPlaybackManager.cpp
+++ b/examples/tv-app/android/java/MediaPlaybackManager.cpp
@@ -97,6 +97,7 @@
CHIP_ERROR MediaPlaybackManager::HandleGetActiveTrack(bool audio, AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
Structs::TrackStruct::Type response;
Structs::TrackAttributesStruct::Type trackAttributes;
response.trackAttributes = Nullable<Structs::TrackAttributesStruct::Type>(trackAttributes);
@@ -170,6 +171,7 @@
CHIP_ERROR MediaPlaybackManager::HandleGetAvailableTracks(bool audio, AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
@@ -179,6 +181,8 @@
VerifyOrExit(mMediaPlaybackManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetAvailableTracksMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
return aEncoder.EncodeList([this, env, audio](const auto & encoder) -> CHIP_ERROR {
jobjectArray trackList = (jobjectArray) env->CallObjectMethod(mMediaPlaybackManagerObject.ObjectRef(),
mGetAvailableTracksMethod, static_cast<jboolean>(audio));
@@ -317,6 +321,7 @@
bool MediaPlaybackManager::HandleActivateTrack(bool audio, const chip::CharSpan & trackId)
{
+ DeviceLayer::StackUnlock unlock;
std::string id(trackId.data(), trackId.size());
jint ret = -1;
@@ -328,9 +333,11 @@
ChipLogProgress(Zcl, "MediaPlaybackManager::HandleActivateAudioTrack");
VerifyOrExit(mMediaPlaybackManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mActivateTrackMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+ env->ExceptionClear();
+
{
UtfString jniid(env, id.c_str());
- env->ExceptionClear();
ret = env->CallIntMethod(mMediaPlaybackManagerObject.ObjectRef(), mActivateTrackMethod, static_cast<jboolean>(audio),
jniid.jniValue());
if (env->ExceptionCheck())
@@ -351,6 +358,7 @@
bool MediaPlaybackManager::HandleDeactivateTextTrack()
{
+ DeviceLayer::StackUnlock unlock;
jint ret = -1;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
@@ -439,6 +447,7 @@
uint64_t MediaPlaybackManager::HandleMediaRequestGetAttribute(MediaPlaybackRequestAttribute attribute)
{
+ DeviceLayer::StackUnlock unlock;
uint64_t ret = std::numeric_limits<uint64_t>::max();
jlong jAttributeValue = -1;
CHIP_ERROR err = CHIP_NO_ERROR;
@@ -450,6 +459,8 @@
VerifyOrExit(mMediaPlaybackManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetAttributeMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
jAttributeValue =
env->CallLongMethod(mMediaPlaybackManagerObject.ObjectRef(), mGetAttributeMethod, static_cast<jint>(attribute));
if (env->ExceptionCheck())
@@ -480,6 +491,7 @@
long MediaPlaybackManager::HandleMediaRequestGetLongAttribute(MediaPlaybackRequestAttribute attribute)
{
+ DeviceLayer::StackUnlock unlock;
long ret = 0;
jlong jAttributeValue = -1;
CHIP_ERROR err = CHIP_NO_ERROR;
@@ -491,6 +503,8 @@
VerifyOrExit(mMediaPlaybackManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mGetAttributeMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+ env->ExceptionClear();
+
jAttributeValue =
env->CallLongMethod(mMediaPlaybackManagerObject.ObjectRef(), mGetAttributeMethod, static_cast<jint>(attribute));
if (env->ExceptionCheck())
@@ -516,6 +530,7 @@
uint64_t deltaPositionMilliseconds)
{
+ DeviceLayer::StackUnlock unlock;
Commands::PlaybackResponse::Type response;
jint ret = -1;
@@ -553,6 +568,7 @@
CHIP_ERROR MediaPlaybackManager::HandleGetSampledPosition(AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
Structs::PlaybackPositionStruct::Type response;
response.updatedAt = 0;
response.position = Nullable<uint64_t>(0);
diff --git a/examples/tv-app/android/java/MessagesManager.cpp b/examples/tv-app/android/java/MessagesManager.cpp
new file mode 100644
index 0000000..9203d7b
--- /dev/null
+++ b/examples/tv-app/android/java/MessagesManager.cpp
@@ -0,0 +1,454 @@
+/**
+ *
+ * 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 "MessagesManager.h"
+#include "TvApp-JNI.h"
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/util/config.h>
+#include <cstdlib>
+#include <jni.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CHIPJNIError.h>
+#include <lib/support/JniReferences.h>
+#include <lib/support/JniTypeWrappers.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters::Messages;
+using namespace chip::Uint8;
+using MessageResponseOption = chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type;
+
+/** @brief Messages Cluster Init
+ *
+ * This function is called when a specific cluster is initialized. It gives the
+ * application an opportunity to take care of cluster initialization procedures.
+ * It is called exactly once for each endpoint where cluster is present.
+ *
+ */
+void emberAfMessagesClusterInitCallback(EndpointId endpoint)
+{
+ ChipLogProgress(Zcl, "------------TV Android App: Messages::PostClusterInit");
+ TvAppJNIMgr().PostClusterInit(chip::app::Clusters::Messages::Id, endpoint);
+}
+
+void MessagesManager::NewManager(jint endpoint, jobject manager)
+{
+ ChipLogProgress(Zcl, "-----TV Android App: Messages::SetDefaultDelegate");
+ MessagesManager * mgr = new MessagesManager();
+ VerifyOrReturn(mgr != nullptr, ChipLogError(Zcl, "Failed to create MessagesManager"));
+ mgr->InitializeWithObjects(manager);
+ chip::app::Clusters::Messages::SetDefaultDelegate(static_cast<EndpointId>(endpoint), mgr);
+}
+
+void MessagesManager::InitializeWithObjects(jobject managerObject)
+{
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Failed to GetEnvForCurrentThread for MessagesManager"));
+
+ VerifyOrReturn(mMessagesManagerObject.Init(managerObject) == CHIP_NO_ERROR,
+ ChipLogError(Zcl, "Failed to init mMessagesManagerObject"));
+
+ jclass managerClass = env->GetObjectClass(managerObject);
+ VerifyOrReturn(managerClass != nullptr, ChipLogError(Zcl, "Failed to get MessagesManager Java class"));
+
+ mGetMessagesMethod = env->GetMethodID(managerClass, "getMessages", "()[Lcom/matter/tv/server/tvapp/Message;");
+ if (mGetMessagesMethod == nullptr)
+ {
+ ChipLogError(Zcl, "Failed to access MessagesManager 'getMessages' method");
+ env->ExceptionClear();
+ }
+
+ mPresentMessagesMethod =
+ env->GetMethodID(managerClass, "presentMessages", "(Ljava/lang/String;IIJILjava/lang/String;Ljava/util/HashMap;)Z");
+ if (mPresentMessagesMethod == nullptr)
+ {
+ ChipLogError(Zcl, "Failed to access MessagesManager 'presentMessages' method");
+ env->ExceptionClear();
+ }
+
+ mCancelMessagesMethod = env->GetMethodID(managerClass, "cancelMessage", "(Ljava/lang/String;)Z");
+ if (mCancelMessagesMethod == nullptr)
+ {
+ ChipLogError(Zcl, "Failed to access MessagesManager 'cancelMessage' method");
+ env->ExceptionClear();
+ }
+}
+
+uint32_t MessagesManager::GetFeatureMap(chip::EndpointId endpoint)
+{
+ if (endpoint >= MATTER_DM_CONTENT_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT)
+ {
+ return kEndpointFeatureMap;
+ }
+
+ BitMask<Feature> FeatureMap;
+ FeatureMap.Set(Feature::kReceivedConfirmation);
+ FeatureMap.Set(Feature::kConfirmationResponse);
+ FeatureMap.Set(Feature::kConfirmationReply);
+ FeatureMap.Set(Feature::kProtectedMessages);
+
+ uint32_t featureMap = FeatureMap.Raw();
+ // forcing to all features since this implementation supports all
+ // Attributes::FeatureMap::Get(endpoint, &featureMap);
+ return featureMap;
+}
+
+CHIP_ERROR MessagesManager::HandleGetMessages(AttributeValueEncoder & aEncoder)
+{
+ DeviceLayer::StackUnlock unlock;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+ JniLocalReferenceScope scope(env);
+
+ env->ExceptionClear();
+
+ ChipLogProgress(Zcl, "Received MessagesManager::HandleGetMessages");
+ VerifyOrExit(mMessagesManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mGetMessagesMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+ return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
+ jobjectArray messagesList =
+ static_cast<jobjectArray>(env->CallObjectMethod(mMessagesManagerObject.ObjectRef(), mGetMessagesMethod));
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(Zcl, "Java exception in MessagesManager::HandleGetMessages");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_ERROR_INCORRECT_STATE;
+ }
+
+ jint length = env->GetArrayLength(messagesList);
+
+ for (jint i = 0; i < length; i++)
+ {
+ std::vector<MessageResponseOption> options;
+ std::vector<JniUtfString *> optionLabels;
+ uint8_t buf[kMessageIdLength];
+
+ chip::app::Clusters::Messages::Structs::MessageStruct::Type message;
+ jobject messageObject = env->GetObjectArrayElement(messagesList, i);
+ jclass messageClass = env->GetObjectClass(messageObject);
+
+ jfieldID getMessageIdField = env->GetFieldID(messageClass, "messageId", "Ljava/lang/String;");
+ jstring jmessageId = static_cast<jstring>(env->GetObjectField(messageObject, getMessageIdField));
+ JniUtfString messageId(env, jmessageId);
+ if (jmessageId != nullptr)
+ {
+ VerifyOrReturnValue(chip::Encoding::HexToBytes(messageId.charSpan().data(), messageId.charSpan().size(), buf,
+ sizeof(buf)) == sizeof(buf),
+ CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(Zcl, "HexToBytes failed"));
+ message.messageID = ByteSpan(buf, sizeof(buf));
+ }
+
+ jfieldID getMessageTextField = env->GetFieldID(messageClass, "messageText", "Ljava/lang/String;");
+ jstring jmessageText = static_cast<jstring>(env->GetObjectField(messageObject, getMessageTextField));
+ JniUtfString messageText(env, jmessageText);
+ if (jmessageText != nullptr)
+ {
+ message.messageText = messageText.charSpan();
+ }
+
+ jfieldID messageControlField = env->GetFieldID(messageClass, "messageControl", "I");
+ jint jmessageControl = env->GetIntField(messageObject, messageControlField);
+ message.messageControl = static_cast<chip::BitMask<MessageControlBitmap>>(static_cast<uint8_t>(jmessageControl));
+
+ jfieldID priorityField = env->GetFieldID(messageClass, "priority", "I");
+ jint jpriority = env->GetIntField(messageObject, priorityField);
+ if (jpriority >= 0)
+ {
+ message.priority = MessagePriorityEnum(static_cast<uint8_t>(jpriority));
+ }
+
+ jfieldID startTimeField = env->GetFieldID(messageClass, "startTime", "J");
+ jlong jstartTime = env->GetLongField(messageObject, startTimeField);
+ if (jstartTime >= 0)
+ {
+ message.startTime = DataModel::Nullable<uint32_t>(static_cast<uint32_t>(jstartTime));
+ }
+
+ jfieldID durationField = env->GetFieldID(messageClass, "duration", "I");
+ jint jduration = env->GetIntField(messageObject, durationField);
+ if (jduration >= 0)
+ {
+ message.duration = DataModel::Nullable<uint16_t>(static_cast<uint16_t>(jduration));
+ }
+
+ jfieldID getResponseOptionsField =
+ env->GetFieldID(messageClass, "responseOptions", "[Lcom/matter/tv/server/tvapp/MessageResponseOption;");
+
+ jobjectArray responsesArray = static_cast<jobjectArray>(env->GetObjectField(messageObject, getResponseOptionsField));
+ jint size = env->GetArrayLength(responsesArray);
+ if (size > 0)
+ {
+ for (jint j = 0; j < size; j++)
+ {
+ MessageResponseOption option;
+
+ jobject responseOptionObject = env->GetObjectArrayElement(responsesArray, j);
+ jclass responseOptionClass = env->GetObjectClass(responseOptionObject);
+
+ jfieldID idField = env->GetFieldID(responseOptionClass, "id", "J");
+ jlong jid = env->GetLongField(responseOptionObject, idField);
+ option.messageResponseID = Optional<uint32_t>(static_cast<uint32_t>(jid));
+
+ jfieldID getLabelField = env->GetFieldID(responseOptionClass, "label", "Ljava/lang/String;");
+ jstring jlabelText = static_cast<jstring>(env->GetObjectField(responseOptionObject, getLabelField));
+ VerifyOrReturnValue(jlabelText != nullptr, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(Zcl, "jlabelText null"));
+ JniUtfString * label = new JniUtfString(env, jlabelText);
+ VerifyOrReturnValue(label != nullptr, CHIP_ERROR_NO_MEMORY, ChipLogError(Zcl, "label null"));
+
+ optionLabels.push_back(label);
+
+ option.label = Optional<CharSpan>(label->charSpan());
+
+ options.push_back(option);
+ }
+
+ message.responses = Optional<DataModel::List<MessageResponseOption>>(
+ DataModel::List<MessageResponseOption>(options.data(), options.size()));
+ }
+ ReturnErrorOnFailure(encoder.Encode(message));
+ for (JniUtfString * optionLabel : optionLabels)
+ {
+ delete optionLabel;
+ }
+ }
+
+ return CHIP_NO_ERROR;
+ });
+
+exit:
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(Zcl, "MessagesManager::HandleGetMessages status error: %s", err.AsString());
+ }
+ return err;
+}
+
+CHIP_ERROR MessagesManager::HandleGetActiveMessageIds(AttributeValueEncoder & aEncoder)
+{
+ DeviceLayer::StackUnlock unlock;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+ JniLocalReferenceScope scope(env);
+
+ ChipLogProgress(Zcl, "Received MessagesManager::HandleGetActiveMessageIds");
+ VerifyOrExit(mMessagesManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mGetMessagesMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+ env->ExceptionClear();
+
+ return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
+ jobjectArray messagesList =
+ static_cast<jobjectArray>(env->CallObjectMethod(mMessagesManagerObject.ObjectRef(), mGetMessagesMethod));
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(Zcl, "Java exception in MessagesManager::HandleGetActiveMessageIds");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_ERROR_INCORRECT_STATE;
+ }
+
+ jint length = env->GetArrayLength(messagesList);
+
+ for (jint i = 0; i < length; i++)
+ {
+ jobject messageObject = env->GetObjectArrayElement(messagesList, i);
+ jclass messageClass = env->GetObjectClass(messageObject);
+
+ jfieldID getMessageIdField = env->GetFieldID(messageClass, "messageId", "Ljava/lang/String;");
+ jstring jmessageId = static_cast<jstring>(env->GetObjectField(messageObject, getMessageIdField));
+ JniUtfString messageId(env, jmessageId);
+ if (jmessageId != nullptr)
+ {
+ uint8_t buf[kMessageIdLength];
+ VerifyOrReturnValue(chip::Encoding::HexToBytes(messageId.charSpan().data(), messageId.charSpan().size(), buf,
+ sizeof(buf)) == sizeof(buf),
+ CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(Zcl, "HexToBytes failed"));
+
+ ReturnErrorOnFailure(encoder.Encode(ByteSpan(buf, sizeof(buf))));
+ }
+ }
+
+ return CHIP_NO_ERROR;
+ });
+
+exit:
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(Zcl, "MessagesManager::HandleGetMessages status error: %s", err.AsString());
+ }
+
+ return err;
+}
+
+CHIP_ERROR MessagesManager::HandlePresentMessagesRequest(
+ const ByteSpan & messageId, const MessagePriorityEnum & priority, const BitMask<MessageControlBitmap> & messageControl,
+ const DataModel::Nullable<uint32_t> & startTime, const DataModel::Nullable<uint16_t> & duration, const CharSpan & messageText,
+ const Optional<DataModel::DecodableList<MessageResponseOption>> & responses)
+{
+ DeviceLayer::StackUnlock unlock;
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+ JniLocalReferenceScope scope(env);
+
+ ChipLogProgress(Zcl, "Received MessagesManager::HandlePresentMessagesRequest");
+ VerifyOrReturnError(mMessagesManagerObject.HasValidObjectRef(), CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "Invalid mMessagesManagerObject"));
+ VerifyOrReturnError(mPresentMessagesMethod != nullptr, CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "mPresentMessagesMethod null"));
+
+ env->ExceptionClear();
+ {
+ char hex_buf[(kMessageIdLength * 2) + 1];
+ VerifyOrReturnError(
+ CHIP_NO_ERROR ==
+ chip::Encoding::BytesToUppercaseHexString(messageId.data(), messageId.size(), hex_buf, sizeof(hex_buf)),
+ CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "BytesToUppercaseHexString failed"));
+
+ jstring jid = env->NewStringUTF(hex_buf);
+ if (jid == nullptr)
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ std::string smessageText(messageText.data(), messageText.size());
+ jstring jmessageText = env->NewStringUTF(smessageText.c_str());
+ if (jmessageText == nullptr)
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ jint jcontrol = static_cast<jint>(messageControl.Raw());
+ jint jduration = -1;
+ if (!duration.IsNull())
+ {
+ jduration = static_cast<jint>(duration.Value());
+ }
+ jlong jstartTime = -1;
+ if (!startTime.IsNull())
+ {
+ jstartTime = static_cast<jlong>(startTime.Value());
+ }
+
+ jint jpriority = static_cast<jint>(priority);
+
+ jclass hashMapClass = env->FindClass("java/util/HashMap");
+ VerifyOrReturnError(hashMapClass != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not find class HashMap"));
+ jmethodID hashMapCtor = env->GetMethodID(hashMapClass, "<init>", "()V");
+ VerifyOrReturnError(hashMapCtor != nullptr, CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "Could not find HashMap constructor"));
+ jobject joptions = env->NewObject(hashMapClass, hashMapCtor);
+ VerifyOrReturnError(joptions != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not create HashMap"));
+
+ if (responses.HasValue())
+ {
+ jmethodID hashMapPut =
+ env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ VerifyOrReturnError(hashMapPut != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not find HashMap put"));
+
+ jclass longClass = env->FindClass("java/lang/Long");
+ VerifyOrReturnError(longClass != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not find class Long"));
+ jmethodID longCtor = env->GetMethodID(longClass, "<init>", "(J)V");
+ VerifyOrReturnError(longCtor != nullptr, CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "Could not find Long constructor"));
+
+ auto iter = responses.Value().begin();
+ while (iter.Next())
+ {
+ auto & response = iter.GetValue();
+
+ std::string label(response.label.Value().data(), response.label.Value().size());
+ jstring jlabel = env->NewStringUTF(label.c_str());
+ if (jlabel == nullptr)
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ jobject jlong = env->NewObject(longClass, longCtor, response.messageResponseID.Value());
+ VerifyOrReturnError(jlong != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Could not create Long"));
+
+ // add to HashMap
+ env->CallObjectMethod(joptions, hashMapPut, jlong, jlabel);
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(DeviceLayer, "Java exception in MessagesManager::HandlePresentMessagesRequest");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_ERROR_INTERNAL;
+ }
+ }
+ }
+
+ env->CallBooleanMethod(mMessagesManagerObject.ObjectRef(), mPresentMessagesMethod, jid, jpriority, jcontrol, jstartTime,
+ jduration, jmessageText, joptions);
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(DeviceLayer, "Java exception in MessagesManager::HandlePresentMessagesRequest");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_ERROR_INTERNAL;
+ }
+ }
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR MessagesManager::HandleCancelMessagesRequest(const DataModel::DecodableList<ByteSpan> & messageIds)
+{
+ DeviceLayer::StackUnlock unlock;
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NULL_OBJECT, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+ JniLocalReferenceScope scope(env);
+
+ ChipLogProgress(Zcl, "Received MessagesManager::HandleCancelMessagesRequest");
+ VerifyOrReturnError(mMessagesManagerObject.HasValidObjectRef(), CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "Invalid mMessagesManagerObject"));
+ VerifyOrReturnError(mCancelMessagesMethod != nullptr, CHIP_ERROR_INCORRECT_STATE,
+ ChipLogError(Zcl, "mCancelMessagesMethod null"));
+
+ env->ExceptionClear();
+
+ auto iter = messageIds.begin();
+ while (iter.Next())
+ {
+ auto & id = iter.GetValue();
+
+ char hex_buf[(kMessageIdLength * 2) + 1];
+ VerifyOrReturnError(CHIP_NO_ERROR ==
+ chip::Encoding::BytesToUppercaseHexString(id.data(), id.size(), hex_buf, sizeof(hex_buf)),
+ CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "BytesToUppercaseHexString failed"));
+
+ jstring jid = env->NewStringUTF(hex_buf);
+ if (jid == nullptr)
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ env->CallBooleanMethod(mMessagesManagerObject.ObjectRef(), mCancelMessagesMethod, jid);
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(DeviceLayer, "Java exception in MessagesManager::HandleCancelMessagesRequest");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return CHIP_ERROR_INTERNAL;
+ }
+ }
+ return CHIP_NO_ERROR;
+}
diff --git a/examples/tv-app/android/java/MessagesManager.h b/examples/tv-app/android/java/MessagesManager.h
new file mode 100644
index 0000000..563192a
--- /dev/null
+++ b/examples/tv-app/android/java/MessagesManager.h
@@ -0,0 +1,63 @@
+/**
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <app/clusters/messages-server/messages-server.h>
+
+#include <iostream>
+#include <list>
+
+#include <jni.h>
+#include <lib/support/JniReferences.h>
+
+class MessagesManager : public chip::app::Clusters::Messages::Delegate
+{
+public:
+ static void NewManager(jint endpoint, jobject manager);
+ void InitializeWithObjects(jobject managerObject);
+
+ // Commands
+ CHIP_ERROR HandlePresentMessagesRequest(
+ const chip::ByteSpan & messageId, const chip::app::Clusters::Messages::MessagePriorityEnum & priority,
+ const chip::BitMask<chip::app::Clusters::Messages::MessageControlBitmap> & messageControl,
+ const chip::app::DataModel::Nullable<uint32_t> & startTime, const chip::app::DataModel::Nullable<uint16_t> & duration,
+ const chip::CharSpan & messageText,
+ const chip::Optional<
+ chip::app::DataModel::DecodableList<chip::app::Clusters::Messages::Structs::MessageResponseOptionStruct::Type>> &
+ responses) override;
+ CHIP_ERROR HandleCancelMessagesRequest(const chip::app::DataModel::DecodableList<chip::ByteSpan> & messageIds) override;
+
+ // Attributes
+ CHIP_ERROR HandleGetMessages(chip::app::AttributeValueEncoder & aEncoder) override;
+ CHIP_ERROR HandleGetActiveMessageIds(chip::app::AttributeValueEncoder & aEncoder) override;
+
+ // Global Attributes
+ uint32_t GetFeatureMap(chip::EndpointId endpoint) override;
+ // uint16_t GetClusterRevision(chip::EndpointId endpoint) override;
+
+private:
+ chip::JniGlobalReference mMessagesManagerObject;
+ jmethodID mGetMessagesMethod = nullptr;
+
+ jmethodID mPresentMessagesMethod = nullptr;
+ jmethodID mCancelMessagesMethod = nullptr;
+
+ // TODO: set this based upon meta data from app
+ static constexpr uint32_t kEndpointFeatureMap = 15;
+ // static constexpr uint16_t kClusterRevision = 1;
+};
diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp
index d2c83b2..82b06e2 100644
--- a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp
+++ b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp
@@ -78,6 +78,7 @@
*/
void JNIMyUserPrompter::PromptForCommissionOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
std::string stringCommissioneeName(commissioneeName);
@@ -119,6 +120,7 @@
void JNIMyUserPrompter::PromptForCommissionPasscode(uint16_t vendorId, uint16_t productId, const char * commissioneeName,
uint16_t pairingHint, const char * pairingInstruction)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
std::string stringCommissioneeName(commissioneeName);
@@ -198,6 +200,7 @@
*/
void JNIMyUserPrompter::PromptCommissioningSucceeded(uint16_t vendorId, uint16_t productId, const char * commissioneeName)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
std::string stringCommissioneeName(commissioneeName);
@@ -234,6 +237,7 @@
*/
void JNIMyUserPrompter::PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error)
{
+ DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
std::string stringCommissioneeName(commissioneeName);
diff --git a/examples/tv-app/android/java/OnOffManager.cpp b/examples/tv-app/android/java/OnOffManager.cpp
index 9a33075..db69474 100644
--- a/examples/tv-app/android/java/OnOffManager.cpp
+++ b/examples/tv-app/android/java/OnOffManager.cpp
@@ -113,6 +113,7 @@
void OnOffManager::HandleOnOffChanged(bool value)
{
+ DeviceLayer::StackUnlock unlock;
ChipLogProgress(Zcl, "OnOffManager::HandleOnOffChanged");
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp
index 6bbc35e..72279c4 100644
--- a/examples/tv-app/android/java/TVApp-JNI.cpp
+++ b/examples/tv-app/android/java/TVApp-JNI.cpp
@@ -27,6 +27,7 @@
#include "LowPowerManager.h"
#include "MediaInputManager.h"
#include "MediaPlaybackManager.h"
+#include "MessagesManager.h"
#include "MyUserPrompter-JNI.h"
#include "OnOffManager.h"
#include "WakeOnLanManager.h"
@@ -137,6 +138,11 @@
MediaPlaybackManager::NewManager(endpoint, manager);
}
+JNI_METHOD(void, setMessagesManager)(JNIEnv *, jobject, jint endpoint, jobject manager)
+{
+ MessagesManager::NewManager(endpoint, manager);
+}
+
JNI_METHOD(void, setChannelManager)(JNIEnv *, jobject, jint endpoint, jobject manager)
{
ChannelManager::NewManager(endpoint, manager);
diff --git a/examples/tv-app/android/java/WakeOnLanManager.cpp b/examples/tv-app/android/java/WakeOnLanManager.cpp
index 5a3093a..a50ddca 100644
--- a/examples/tv-app/android/java/WakeOnLanManager.cpp
+++ b/examples/tv-app/android/java/WakeOnLanManager.cpp
@@ -51,6 +51,7 @@
CHIP_ERROR WakeOnLanManager::HandleGetMacAddress(chip::app::AttributeValueEncoder & aEncoder)
{
+ DeviceLayer::StackUnlock unlock;
jobject javaMac;
CHIP_ERROR err = CHIP_NO_ERROR;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Clusters.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Clusters.java
index d699ca6..5928c1b 100644
--- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Clusters.java
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Clusters.java
@@ -129,7 +129,7 @@
public static final long ClusterId_ApplicationBasic = 0x0000050D;
public static final long ClusterId_AccountLogin = 0x0000050E;
public static final long ClusterId_TestCluster = 0xFFF1FC05;
- public static final long ClusterId_Messaging = 0x00000703;
+ public static final long ClusterId_Messaging = 0x00000097;
public static final long ClusterId_ApplianceIdentification = 0x00000B00;
public static final long ClusterId_MeterIdentification = 0x00000B01;
public static final long ClusterId_ApplianceEventsAndAlert = 0x00000B02;
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Message.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Message.java
new file mode 100644
index 0000000..c194ffb
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Message.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.matter.tv.server.tvapp;
+
+public class Message {
+
+ public String messageId;
+ public int priority;
+ public int messageControl;
+ public long startTime;
+ public int duration;
+ public String messageText;
+ public MessageResponseOption responseOptions[];
+
+ public Message(
+ String messageId,
+ int priority,
+ int messageControl,
+ long startTime,
+ int duration,
+ String messageText,
+ MessageResponseOption responseOptions[]) {
+ this.messageId = messageId;
+ this.priority = priority;
+ this.messageControl = messageControl;
+ this.startTime = startTime;
+ this.duration = duration;
+ this.messageText = messageText;
+ this.responseOptions = responseOptions;
+ }
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessageResponseOption.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessageResponseOption.java
new file mode 100644
index 0000000..5d8e77d
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessageResponseOption.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.matter.tv.server.tvapp;
+
+public class MessageResponseOption {
+ public long id = -1;
+ public String label = "na";
+
+ public MessageResponseOption(long id, String label) {
+ this.id = id;
+ this.label = label;
+ }
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManager.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManager.java
new file mode 100644
index 0000000..0a56808
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.matter.tv.server.tvapp;
+
+import java.util.HashMap;
+
+public interface MessagesManager {
+
+ Message[] getMessages();
+
+ boolean presentMessages(
+ String messageId,
+ int priority,
+ int messageControl,
+ long startTime,
+ int duration,
+ String messageText,
+ HashMap<Long, String> responseOptions);
+
+ boolean cancelMessage(String messageId);
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java
new file mode 100644
index 0000000..63fef69
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.matter.tv.server.tvapp;
+
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MessagesManagerStub implements MessagesManager {
+ private static final String TAG = MessagesManagerStub.class.getSimpleName();
+
+ private int endpoint = -1;
+
+ private Map<String, Message> messages = new HashMap<String, Message>();
+
+ public MessagesManagerStub(int endpoint) {
+ this.endpoint = endpoint;
+ Log.d(TAG, "MessagesManagerStub: at " + this.endpoint);
+
+ HashMap<Long, String> responseOptions = new HashMap<Long, String>();
+ responseOptions.put(new Long(1), "Yes");
+ responseOptions.put(new Long(2), "No");
+ presentMessages(
+ "31323334353637383930313233343536", 1, 1, 30, 60, "TestMessage", responseOptions);
+ Log.d(TAG, "MessagesManagerStub: added dummy message");
+ }
+
+ @Override
+ public Message[] getMessages() {
+ Log.d(TAG, "getMessages: at " + this.endpoint);
+ return messages.values().toArray(new Message[0]);
+ }
+
+ @Override
+ public boolean presentMessages(
+ String messageId,
+ int priority,
+ int messageControl,
+ long startTime,
+ int duration,
+ String messageText,
+ HashMap<Long, String> responseOptions) {
+ Log.d(
+ TAG, "presentMessages: at " + this.endpoint + " id:" + messageId + " text:" + messageText);
+ MessageResponseOption[] options = new MessageResponseOption[responseOptions.size()];
+ int i = 0;
+
+ for (Map.Entry<Long, String> set : responseOptions.entrySet()) {
+ Log.d(TAG, "presentMessages option: key:" + set.getKey() + " value:" + set.getValue());
+ options[i] = new MessageResponseOption(set.getKey().longValue(), set.getValue());
+ i++;
+ }
+
+ messages.put(
+ messageId,
+ new Message(
+ messageId, priority, messageControl, startTime, duration, messageText, options));
+ return true;
+ }
+
+ @Override
+ public boolean cancelMessage(String messageId) {
+ Log.d(TAG, "cancelMessage: at " + this.endpoint + " messageId:" + messageId);
+ messages.remove(messageId);
+ return true; // per spec, succeed unless error
+ }
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
index d8bb564..eaf207e 100644
--- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
@@ -57,6 +57,8 @@
public native void setMediaPlaybackManager(int endpoint, MediaPlaybackManager manager);
+ public native void setMessagesManager(int endpoint, MessagesManager manager);
+
public native void setChannelManager(int endpoint, ChannelManager manager);
public native void setOnOffManager(int endpoint, OnOffManager manager);