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);