Add Android tv server (#11245)

* added app server jni

* added getQrCodeFromPayload in SetupPayloadParser

* added TVServer app for android

* fix BLE error

* fix ci issue

* fix restyled errors

* fix ci issue

* fix Tests / Test Suites - Linux (tsan) ci issue

* fix zap issue, we should not add unneed functions in zap but fix undefined issue
diff --git a/BUILD.gn b/BUILD.gn
index 3753f04..57d4f38 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -125,6 +125,7 @@
 
     if (current_os == "android") {
       deps += [
+        "${chip_root}/src/app/server/java",
         "${chip_root}/src/controller/java",
         "${chip_root}/src/platform/android:java",
         "${chip_root}/src/setup_payload/java",
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 9101fbb..d26edeb 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -435,6 +435,11 @@
     "-ffunction-sections",
     "-fdata-sections",
   ]
+
+  # do not export functions in jni, or --gc-sections will not work and will be some undefined issues
+  if (current_os == "android") {
+    cflags += [ "-fvisibility=hidden" ]
+  }
 }
 
 config("stack_protector_default") {
diff --git a/examples/tv-app/android/BUILD.gn b/examples/tv-app/android/BUILD.gn
new file mode 100644
index 0000000..d071cdc
--- /dev/null
+++ b/examples/tv-app/android/BUILD.gn
@@ -0,0 +1,61 @@
+# Copyright (c) 2021 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.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/build/chip/tools.gni")
+
+# Todo: copy from examples/tv-app/linux but later will be moved to jni and upto java workd
+source_set("android-tv-app") {
+  sources = [
+    "${chip_root}/examples/tv-app/tv-common/include/CHIPProjectAppConfig.h",
+    "include/account-login/AccountLoginManager.cpp",
+    "include/account-login/AccountLoginManager.h",
+    "include/application-basic/ApplicationBasicManager.cpp",
+    "include/application-basic/ApplicationBasicManager.h",
+    "include/application-launcher/ApplicationLauncherManager.cpp",
+    "include/application-launcher/ApplicationLauncherManager.h",
+    "include/audio-output/AudioOutputManager.cpp",
+    "include/audio-output/AudioOutputManager.h",
+    "include/cluster-change-attribute.cpp",
+    "include/cluster-init.cpp",
+    "include/content-launcher/ContentLauncherManager.cpp",
+    "include/content-launcher/ContentLauncherManager.h",
+    "include/endpoint-configuration/EndpointConfigurationStorage.cpp",
+    "include/endpoint-configuration/EndpointConfigurationStorage.h",
+    "include/keypad-input/KeypadInputManager.cpp",
+    "include/keypad-input/KeypadInputManager.h",
+    "include/low-power/LowPowerManager.cpp",
+    "include/low-power/LowPowerManager.h",
+    "include/media-input/MediaInputManager.cpp",
+    "include/media-input/MediaInputManager.h",
+    "include/media-playback/MediaPlaybackManager.cpp",
+    "include/media-playback/MediaPlaybackManager.h",
+    "include/target-navigator/TargetNavigatorManager.cpp",
+    "include/target-navigator/TargetNavigatorManager.h",
+    "include/tv-channel/TvChannelManager.cpp",
+    "include/tv-channel/TvChannelManager.h",
+    "include/wake-on-lan/WakeOnLanManager.cpp",
+    "include/wake-on-lan/WakeOnLanManager.h",
+  ]
+
+  deps = [
+    "${chip_root}/examples/tv-app/tv-common",
+    "${chip_root}/src/lib",
+    "${chip_root}/third_party/inipp",
+  ]
+
+  cflags = [ "-Wconversion" ]
+}
diff --git a/examples/tv-app/android/include/account-login/AccountLoginManager.cpp b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp
new file mode 100644
index 0000000..f599b71
--- /dev/null
+++ b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp
@@ -0,0 +1,80 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "AccountLoginManager.h"
+#include <app-common/zap-generated/attribute-id.h>
+#include <app-common/zap-generated/attribute-type.h>
+#include <app-common/zap-generated/cluster-id.h>
+#include <app-common/zap-generated/command-id.h>
+#include <app-common/zap-generated/enums.h>
+#include <app/Command.h>
+#include <app/util/af.h>
+
+using namespace std;
+
+bool AccountLoginManager::isUserLoggedIn(string requestTempAccountIdentifier, string requestSetupPin)
+{
+    // TODO: Fix hardcoding length of strings
+    requestTempAccountIdentifier = requestTempAccountIdentifier.substr(0, 4);
+    requestSetupPin              = requestSetupPin.substr(0, 10);
+    for (auto it = accounts.cbegin(); it != accounts.cend(); ++it)
+    {
+        ChipLogProgress(Zcl, "temporary account id: %s", it->first.c_str());
+        ChipLogProgress(Zcl, "setup pin %s", it->second.c_str());
+    }
+
+    if (accounts.find(requestTempAccountIdentifier) != accounts.end())
+    {
+        bool found = accounts[requestTempAccountIdentifier] == requestSetupPin;
+        if (!found)
+        {
+            ChipLogError(Zcl, "User is not logged in, failed to match request setup pin.");
+        }
+        return found;
+    }
+    else
+    {
+        ChipLogError(Zcl, "User is not logged in, failed to find temp account identifier.");
+        return false;
+    }
+}
+
+void AccountLoginManager::setTempAccountIdentifierForPin(string tempAccountIdentifier, string setupPin)
+{
+    // TODO: Fix hardcoding length of strings
+    string tempId    = tempAccountIdentifier.substr(0, 4);
+    accounts[tempId] = setupPin;
+}
+
+string AccountLoginManager::proxySetupPinRequest(string requestTempAccountIdentifier, chip::EndpointId endpoint)
+{
+    // TODO: Insert your code here to send temp account identifier request
+    return "tempPin123";
+}
+
+bool accountLoginClusterIsUserLoggedIn(std::string requestTempAccountIdentifier, std::string requestSetupPin)
+{
+    return AccountLoginManager().GetInstance().isUserLoggedIn(requestTempAccountIdentifier, requestSetupPin);
+}
+
+std::string accountLoginClusterGetSetupPin(std::string requestTempAccountIdentifier, chip::EndpointId endpoint)
+{
+    string responseSetupPin = AccountLoginManager().proxySetupPinRequest(requestTempAccountIdentifier, endpoint);
+    AccountLoginManager().GetInstance().setTempAccountIdentifierForPin(requestTempAccountIdentifier, responseSetupPin);
+    return responseSetupPin;
+}
diff --git a/examples/tv-app/android/include/account-login/AccountLoginManager.h b/examples/tv-app/android/include/account-login/AccountLoginManager.h
new file mode 100644
index 0000000..7e1be63
--- /dev/null
+++ b/examples/tv-app/android/include/account-login/AccountLoginManager.h
@@ -0,0 +1,41 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/util/af-types.h>
+#include <lib/core/CHIPError.h>
+#include <map>
+#include <string>
+
+class AccountLoginManager
+{
+public:
+    bool isUserLoggedIn(std::string requestTempAccountIdentifier, std::string requestSetupPin);
+    std::string proxySetupPinRequest(std::string requestTempAccountIdentifier, chip::EndpointId endpoint);
+    void setTempAccountIdentifierForPin(std::string requestTempAccountIdentifier, std::string requestSetupPin);
+
+    static AccountLoginManager & GetInstance()
+    {
+        static AccountLoginManager instance;
+        return instance;
+    }
+
+private:
+    std::map<std::string, std::string> accounts;
+};
diff --git a/examples/tv-app/android/include/application-basic/Application.h b/examples/tv-app/android/include/application-basic/Application.h
new file mode 100644
index 0000000..bdea7a7
--- /dev/null
+++ b/examples/tv-app/android/include/application-basic/Application.h
@@ -0,0 +1,32 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app-common/zap-generated/enums.h>
+
+struct Application
+{
+    char vendorName[32]                  = "";
+    char name[32]                        = "";
+    char id[32]                          = "";
+    uint16_t vendorId                    = 0;
+    uint16_t productId                   = 0;
+    uint16_t catalogVendorId             = 0;
+    EmberAfApplicationBasicStatus status = EMBER_ZCL_APPLICATION_BASIC_STATUS_STOPPED;
+};
diff --git a/examples/tv-app/android/include/application-basic/ApplicationBasicManager.cpp b/examples/tv-app/android/include/application-basic/ApplicationBasicManager.cpp
new file mode 100644
index 0000000..e3c893f
--- /dev/null
+++ b/examples/tv-app/android/include/application-basic/ApplicationBasicManager.cpp
@@ -0,0 +1,161 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "ApplicationBasicManager.h"
+#include <app-common/zap-generated/attribute-id.h>
+#include <app-common/zap-generated/attribute-type.h>
+#include <app-common/zap-generated/cluster-id.h>
+#include <app-common/zap-generated/command-id.h>
+#include <app-common/zap-generated/enums.h>
+#include <app/Command.h>
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+#include <lib/support/ZclString.h>
+
+#include <inipp/inipp.h>
+
+using namespace chip;
+
+CHIP_ERROR ApplicationBasicManager::Init()
+{
+    CHIP_ERROR err                                       = CHIP_NO_ERROR;
+    EndpointConfigurationStorage & endpointConfiguration = EndpointConfigurationStorage::GetInstance();
+    err                                                  = endpointConfiguration.Init();
+    SuccessOrExit(err);
+    es = &endpointConfiguration;
+exit:
+    return err;
+}
+
+void ApplicationBasicManager::store(chip::EndpointId endpoint, Application * application)
+{
+    uint8_t bufferMemory[64];
+    MutableByteSpan zclString(bufferMemory);
+
+    MakeZclCharString(zclString, application->vendorName);
+    EmberAfStatus vendorNameStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_VENDOR_NAME_ATTRIBUTE_ID,
+                                    zclString.data(), ZCL_CHAR_STRING_ATTRIBUTE_TYPE);
+    if (vendorNameStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store vendor name attribute.");
+    }
+
+    EmberAfStatus vendorIdStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_VENDOR_ID_ATTRIBUTE_ID,
+                                    (uint8_t *) &application->vendorId, ZCL_INT16U_ATTRIBUTE_TYPE);
+    if (vendorIdStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store vendor id attribute.");
+    }
+
+    MakeZclCharString(zclString, application->name);
+    EmberAfStatus nameStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_NAME_ATTRIBUTE_ID, zclString.data(),
+                                    ZCL_CHAR_STRING_ATTRIBUTE_TYPE);
+    if (nameStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store name attribute.");
+    }
+
+    EmberAfStatus productIdStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_PRODUCT_ID_ATTRIBUTE_ID,
+                                    (uint8_t *) &application->productId, ZCL_INT16U_ATTRIBUTE_TYPE);
+    if (productIdStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store product id attribute.");
+    }
+
+    MakeZclCharString(zclString, application->id);
+    EmberAfStatus idStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_ID_ATTRIBUTE_ID, zclString.data(),
+                                    ZCL_CHAR_STRING_ATTRIBUTE_TYPE);
+    if (idStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store id attribute.");
+    }
+
+    EmberAfStatus catalogVendorIdStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_CATALOG_VENDOR_ID_ATTRIBUTE_ID,
+                                    (uint8_t *) &application->catalogVendorId, ZCL_INT16U_ATTRIBUTE_TYPE);
+    if (catalogVendorIdStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store catalog vendor id attribute.");
+    }
+
+    EmberAfStatus applicationStatus =
+        emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_BASIC_CLUSTER_ID, ZCL_APPLICATION_STATUS_ATTRIBUTE_ID,
+                                    (uint8_t *) &application->status, ZCL_ENUM8_ATTRIBUTE_TYPE);
+    if (applicationStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store status attribute.");
+    }
+}
+
+Application ApplicationBasicManager::getApplicationForEndpoint(chip::EndpointId endpoint)
+{
+    Application app = {};
+    uint16_t size   = static_cast<uint16_t>(sizeof(app.name));
+
+    std::string section = "endpoint" + std::to_string(endpoint);
+
+    CHIP_ERROR err = es->get(section, "name", app.name, size);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app name. Error:%s", chip::ErrorStr(err));
+    }
+
+    err = es->get(section, "vendorName", app.vendorName, size);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app vendor name. Error:%s", chip::ErrorStr(err));
+    }
+
+    err = es->get(section, "id", app.id, size);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app id. Error:%s", chip::ErrorStr(err));
+    }
+
+    err = es->get(section, "catalogVendorId", app.catalogVendorId);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app catalog vendor id. Error:%s", chip::ErrorStr(err));
+    }
+
+    err = es->get(section, "productId", app.productId);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app product id. Error:%s", chip::ErrorStr(err));
+    }
+
+    err = es->get(section, "vendorId", app.vendorId);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get app vendor id. Error:%s", chip::ErrorStr(err));
+    }
+
+    return app;
+}
+
+bool applicationBasicClusterChangeApplicationStatus(EmberAfApplicationBasicStatus status, chip::EndpointId endpoint)
+{
+    // TODO: Insert code here
+    ChipLogProgress(Zcl, "Sent an application status change request %d for endpoint %d", status, endpoint);
+    return true;
+}
diff --git a/examples/tv-app/android/include/application-basic/ApplicationBasicManager.h b/examples/tv-app/android/include/application-basic/ApplicationBasicManager.h
new file mode 100644
index 0000000..d8c8d21
--- /dev/null
+++ b/examples/tv-app/android/include/application-basic/ApplicationBasicManager.h
@@ -0,0 +1,43 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/util/af-types.h>
+#include <lib/core/CHIPError.h>
+
+#include "../endpoint-configuration/EndpointConfigurationStorage.h"
+#include "Application.h"
+#include <vector>
+
+class ApplicationBasicManager
+{
+public:
+    CHIP_ERROR Init();
+    void store(chip::EndpointId endpoint, Application * application);
+    Application getApplicationForEndpoint(chip::EndpointId endpoint);
+
+    static ApplicationBasicManager & GetInstance()
+    {
+        static ApplicationBasicManager instance;
+        return instance;
+    }
+
+private:
+    EndpointConfigurationStorage * es = nullptr;
+};
diff --git a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp b/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp
new file mode 100644
index 0000000..f193ce3
--- /dev/null
+++ b/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp
@@ -0,0 +1,58 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "ApplicationLauncherManager.h"
+#include <app-common/zap-generated/af-structs.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/clusters/application-launcher-server/application-launcher-server.h>
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+
+using namespace std;
+
+CHIP_ERROR ApplicationLauncherManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+CHIP_ERROR ApplicationLauncherManager::proxyGetApplicationList(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        ReturnErrorOnFailure(encoder.Encode(123u));
+        ReturnErrorOnFailure(encoder.Encode(456u));
+        return CHIP_NO_ERROR;
+    });
+}
+
+ApplicationLauncherResponse applicationLauncherClusterLaunchApp(ApplicationLauncherApp application, std::string data)
+{
+    // TODO: Insert your code
+    ApplicationLauncherResponse response;
+    const char * testData = "data";
+    response.data         = (uint8_t *) testData;
+    response.status       = EMBER_ZCL_APPLICATION_LAUNCHER_STATUS_SUCCESS;
+    // TODO: Update once storing a structure attribute is supported
+    // emberAfWriteServerAttribute(endpoint, ZCL_APPLICATION_LAUNCH_CLUSTER_ID, ZCL_APPLICATION_LAUNCHER_CURRENT_APP_APPLICATION_ID,
+    //                             (uint8_t *) &application, ZCL_STRUCT_ATTRIBUTE_TYPE);
+
+    return response;
+}
diff --git a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h b/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h
new file mode 100644
index 0000000..718321e
--- /dev/null
+++ b/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h
@@ -0,0 +1,32 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/AttributeAccessInterface.h>
+#include <lib/core/CHIPError.h>
+
+#include <string>
+#include <vector>
+
+class ApplicationLauncherManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetApplicationList(chip::app::AttributeValueEncoder & aEncoder);
+};
diff --git a/examples/tv-app/android/include/audio-output/AudioOutputManager.cpp b/examples/tv-app/android/include/audio-output/AudioOutputManager.cpp
new file mode 100644
index 0000000..61018e7
--- /dev/null
+++ b/examples/tv-app/android/include/audio-output/AudioOutputManager.cpp
@@ -0,0 +1,71 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "AudioOutputManager.h"
+
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+CHIP_ERROR AudioOutputManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // TODO: Store feature map once it is supported
+    map<string, bool> featureMap;
+    featureMap["NU"] = true;
+
+    return err;
+}
+
+CHIP_ERROR AudioOutputManager::proxyGetListOfAudioOutputInfo(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        int maximumVectorSize = 3;
+        char name[]           = "exampleName";
+
+        for (int i = 0; i < maximumVectorSize; ++i)
+        {
+            chip::app::Clusters::AudioOutput::Structs::AudioOutputInfo::Type audioOutputInfo;
+            audioOutputInfo.outputType = EMBER_ZCL_AUDIO_OUTPUT_TYPE_HDMI;
+            audioOutputInfo.name       = chip::CharSpan(name, sizeof(name) - 1);
+            audioOutputInfo.index      = static_cast<uint8_t>(1 + i);
+            ReturnErrorOnFailure(encoder.Encode(audioOutputInfo));
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+bool audioOutputClusterSelectOutput(uint8_t index)
+{
+    // TODO: Insert code here
+    return true;
+}
+bool audioOutputClusterRenameOutput(uint8_t index, const chip::CharSpan & name)
+{
+    // TODO: Insert code here
+    return true;
+}
diff --git a/examples/tv-app/android/include/audio-output/AudioOutputManager.h b/examples/tv-app/android/include/audio-output/AudioOutputManager.h
new file mode 100644
index 0000000..63d4554
--- /dev/null
+++ b/examples/tv-app/android/include/audio-output/AudioOutputManager.h
@@ -0,0 +1,30 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/AttributeAccessInterface.h>
+
+#include <lib/core/CHIPError.h>
+#include <vector>
+class AudioOutputManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetListOfAudioOutputInfo(chip::app::AttributeValueEncoder & aEncoder);
+};
diff --git a/examples/tv-app/android/include/cluster-change-attribute.cpp b/examples/tv-app/android/include/cluster-change-attribute.cpp
new file mode 100644
index 0000000..b82682e
--- /dev/null
+++ b/examples/tv-app/android/include/cluster-change-attribute.cpp
@@ -0,0 +1,67 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/ConcreteAttributePath.h>
+#include <lib/support/logging/CHIPLogging.h>
+
+using namespace chip;
+using namespace ::chip::app::Clusters;
+
+enum TvCommand
+{
+    PowerToggle,
+    MuteToggle
+};
+
+void runTvCommand(TvCommand command)
+{
+    switch (command)
+    {
+    case PowerToggle:
+        // TODO: Insert your code here to send power toggle command
+        break;
+    case MuteToggle:
+        // TODO: Insert your code here to send mute toggle command
+        break;
+
+    default:
+        break;
+    }
+}
+
+void MatterAfPostAttributeChangeCallback(const app::ConcreteAttributePath & attributePath, uint8_t mask, uint8_t type,
+                                         uint16_t size, uint8_t * value)
+{
+    if (attributePath.mClusterId == OnOff::Id && attributePath.mAttributeId == OnOff::Attributes::OnOff::Id)
+    {
+        ChipLogProgress(Zcl, "Received on/off command for cluster id: " ChipLogFormatMEI, ChipLogValueMEI(OnOff::Id));
+
+        if (attributePath.mEndpointId == 0)
+        {
+            ChipLogProgress(Zcl, "Execute POWER_TOGGLE");
+            runTvCommand(PowerToggle);
+        }
+        else if (attributePath.mEndpointId == 1)
+        {
+            ChipLogProgress(Zcl, "Execute MUTE_TOGGLE");
+            runTvCommand(MuteToggle);
+        }
+    }
+}
diff --git a/examples/tv-app/android/include/cluster-init.cpp b/examples/tv-app/android/include/cluster-init.cpp
new file mode 100644
index 0000000..3d7bee4
--- /dev/null
+++ b/examples/tv-app/android/include/cluster-init.cpp
@@ -0,0 +1,289 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "application-basic/ApplicationBasicManager.h"
+#include "application-launcher/ApplicationLauncherManager.h"
+#include "audio-output/AudioOutputManager.h"
+#include "content-launcher/ContentLauncherManager.h"
+#include "media-input/MediaInputManager.h"
+#include "target-navigator/TargetNavigatorManager.h"
+#include "tv-channel/TvChannelManager.h"
+#include "wake-on-lan/WakeOnLanManager.h"
+
+#include <app-common/zap-generated/attribute-id.h>
+#include <app-common/zap-generated/cluster-id.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/AttributeAccessInterface.h>
+#include <app/util/attribute-storage.h>
+
+using namespace chip;
+
+namespace {
+template <typename Manager, typename AttrTypeInfo, CHIP_ERROR (Manager::*Getter)(app::AttributeValueEncoder &)>
+class TvAttrAccess : public app::AttributeAccessInterface
+{
+public:
+    TvAttrAccess() : app::AttributeAccessInterface(Optional<EndpointId>::Missing(), AttrTypeInfo::GetClusterId()) {}
+
+    CHIP_ERROR Read(const app::ConcreteAttributePath & aPath, app::AttributeValueEncoder & aEncoder) override
+    {
+        if (aPath.mAttributeId == AttrTypeInfo::GetAttributeId())
+        {
+            return (Manager().*Getter)(aEncoder);
+        }
+
+        return CHIP_NO_ERROR;
+    }
+};
+
+} // anonymous namespace
+
+/** @brief Application Basic 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfApplicationBasicClusterInitCallback(chip::EndpointId endpoint)
+{
+    CHIP_ERROR err                     = CHIP_NO_ERROR;
+    ApplicationBasicManager & aManager = ApplicationBasicManager::GetInstance();
+    err                                = aManager.Init();
+    if (CHIP_NO_ERROR == err)
+    {
+        Application application = aManager.getApplicationForEndpoint(endpoint);
+        aManager.store(endpoint, &application);
+    }
+    else
+    {
+        ChipLogError(Zcl, "Failed to store application for endpoint: %d. Error:%s", endpoint, chip::ErrorStr(err));
+    }
+}
+
+/** @brief Wake On LAN 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfWakeOnLanClusterInitCallback(chip::EndpointId endpoint)
+{
+    CHIP_ERROR err                = CHIP_NO_ERROR;
+    WakeOnLanManager & wolManager = WakeOnLanManager::GetInstance();
+    err                           = wolManager.Init();
+    if (CHIP_NO_ERROR == err)
+    {
+        char macAddress[32] = "";
+        wolManager.setMacAddress(endpoint, macAddress);
+        wolManager.store(endpoint, macAddress);
+    }
+    else
+    {
+        ChipLogError(Zcl, "Failed to store mac address for endpoint: %d. Error:%s", endpoint, chip::ErrorStr(err));
+    }
+}
+
+namespace {
+
+TvAttrAccess<TvChannelManager, app::Clusters::TvChannel::Attributes::TvChannelList::TypeInfo,
+             &TvChannelManager::proxyGetTvChannelList>
+    gTvChannelAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Tv Channel  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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfTvChannelClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gTvChannelAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
+
+namespace {
+
+TvAttrAccess<ApplicationLauncherManager, app::Clusters::ApplicationLauncher::Attributes::ApplicationLauncherList::TypeInfo,
+             &ApplicationLauncherManager::proxyGetApplicationList>
+    gApplicationLauncherAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Application Launcher  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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfApplicationLauncherClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gApplicationLauncherAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
+
+namespace {
+
+TvAttrAccess<AudioOutputManager, app::Clusters::AudioOutput::Attributes::AudioOutputList::TypeInfo,
+             &AudioOutputManager::proxyGetListOfAudioOutputInfo>
+    gAudioOutputAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Audio Output 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfAudioOutputClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gAudioOutputAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
+
+namespace {
+
+class ContentLauncherAttrAccess : public app::AttributeAccessInterface
+{
+public:
+    ContentLauncherAttrAccess() : app::AttributeAccessInterface(Optional<EndpointId>::Missing(), app::Clusters::ContentLauncher::Id)
+    {}
+
+    CHIP_ERROR Read(const app::ConcreteAttributePath & aPath, app::AttributeValueEncoder & aEncoder) override
+    {
+        if (aPath.mAttributeId == app::Clusters::ContentLauncher::Attributes::AcceptsHeaderList::Id)
+        {
+            return ContentLauncherManager().proxyGetAcceptsHeader(aEncoder);
+        }
+
+        if (aPath.mAttributeId == app::Clusters::ContentLauncher::Attributes::SupportedStreamingTypes::Id)
+        {
+            return ContentLauncherManager().proxyGetSupportedStreamingTypes(aEncoder);
+        }
+
+        return CHIP_NO_ERROR;
+    }
+};
+
+ContentLauncherAttrAccess gContentLauncherAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Content Launch 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfContentLauncherClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gContentLauncherAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
+
+namespace {
+
+TvAttrAccess<MediaInputManager, app::Clusters::MediaInput::Attributes::MediaInputList::TypeInfo,
+             &MediaInputManager::proxyGetInputList>
+    gMediaInputAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Media Input 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfMediaInputClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gMediaInputAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
+
+namespace {
+
+TvAttrAccess<TargetNavigatorManager, app::Clusters::TargetNavigator::Attributes::TargetNavigatorList::TypeInfo,
+             &TargetNavigatorManager::proxyGetTargetInfoList>
+    gTargetNavigatorAttrAccess;
+
+} // anonymous namespace
+
+/** @brief Target Navigator 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.
+ *
+ * @param endpoint   Ver.: always
+ *
+ */
+void emberAfTargetNavigatorClusterInitCallback(EndpointId endpoint)
+{
+    static bool attrAccessRegistered = false;
+    if (!attrAccessRegistered)
+    {
+        registerAttributeAccessOverride(&gTargetNavigatorAttrAccess);
+        attrAccessRegistered = true;
+    }
+}
diff --git a/examples/tv-app/android/include/content-launcher/ContentLauncherManager.cpp b/examples/tv-app/android/include/content-launcher/ContentLauncherManager.cpp
new file mode 100644
index 0000000..001fe84
--- /dev/null
+++ b/examples/tv-app/android/include/content-launcher/ContentLauncherManager.cpp
@@ -0,0 +1,138 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "ContentLauncherManager.h"
+
+#include <app-common/zap-generated/attribute-id.h>
+#include <app-common/zap-generated/attribute-type.h>
+#include <app-common/zap-generated/cluster-id.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/command-id.h>
+
+#include <app/Command.h>
+#include <app/CommandHandler.h>
+#include <app/ConcreteCommandPath.h>
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
+
+#include <map>
+
+using namespace std;
+
+CHIP_ERROR ContentLauncherManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // TODO: Store feature map once it is supported
+    map<string, bool> featureMap;
+    featureMap["CS"] = true;
+    featureMap["UP"] = true;
+    featureMap["WA"] = true;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+CHIP_ERROR ContentLauncherManager::proxyGetAcceptsHeader(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        char headerExample[]  = "exampleHeader";
+        int maximumVectorSize = 1;
+
+        for (uint16_t i = 0; i < maximumVectorSize; ++i)
+        {
+            ReturnErrorOnFailure(encoder.Encode(chip::ByteSpan(chip::Uint8::from_char(headerExample), sizeof(headerExample) - 1)));
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+CHIP_ERROR ContentLauncherManager::proxyGetSupportedStreamingTypes(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        ReturnErrorOnFailure(encoder.Encode(EMBER_ZCL_CONTENT_LAUNCH_STREAMING_TYPE_DASH));
+        ReturnErrorOnFailure(encoder.Encode(EMBER_ZCL_CONTENT_LAUNCH_STREAMING_TYPE_HLS));
+        return CHIP_NO_ERROR;
+    });
+}
+
+ContentLaunchResponse ContentLauncherManager::proxyLaunchContentRequest(list<ContentLaunchParamater> parameterList, bool autoplay,
+                                                                        string data)
+{
+    // TODO: Insert code here
+    ContentLaunchResponse response;
+    response.data   = "Example data";
+    response.status = EMBER_ZCL_CONTENT_LAUNCH_STATUS_SUCCESS;
+    return response;
+}
+ContentLaunchResponse ContentLauncherManager::proxyLaunchUrlRequest(string contentUrl, string displayString,
+                                                                    ContentLaunchBrandingInformation brandingInformation)
+{
+    // TODO: Insert code here
+    ContentLaunchResponse response;
+    response.data   = "Example data";
+    response.status = EMBER_ZCL_CONTENT_LAUNCH_STATUS_SUCCESS;
+    return response;
+}
+
+static void sendResponse(const char * responseName, ContentLaunchResponse launchResponse, chip::CommandId commandId)
+{
+    emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT), ZCL_CONTENT_LAUNCH_CLUSTER_ID,
+                              commandId, "us", launchResponse.status, &launchResponse.data);
+
+    EmberStatus status = emberAfSendResponse();
+    if (status != EMBER_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to send %s. Error:%d", responseName, static_cast<int>(status));
+    }
+}
+
+bool emberAfContentLauncherClusterLaunchContentCallback(
+    chip::app::CommandHandler * command, const chip::app::ConcreteCommandPath & commandPath,
+    const chip::app::Clusters::ContentLauncher::Commands::LaunchContent::DecodableType & commandData)
+{
+    auto & autoplay = commandData.autoPlay;
+    auto & data     = commandData.data;
+
+    string dataString(data.data(), data.size());
+    list<ContentLaunchParamater> parameterList;
+    ContentLaunchResponse response = ContentLauncherManager().proxyLaunchContentRequest(parameterList, autoplay, dataString);
+    sendResponse("LaunchContent", response, ZCL_LAUNCH_CONTENT_RESPONSE_COMMAND_ID);
+    return true;
+}
+
+bool emberAfContentLauncherClusterLaunchURLCallback(
+    chip::app::CommandHandler * command, const chip::app::ConcreteCommandPath & commandPath,
+    const chip::app::Clusters::ContentLauncher::Commands::LaunchURL::DecodableType & commandData)
+{
+    auto & contentUrl    = commandData.contentURL;
+    auto & displayString = commandData.displayString;
+
+    string contentUrlString(contentUrl.data(), contentUrl.size());
+    string displayStringString(displayString.data(), displayString.size());
+    ContentLaunchBrandingInformation brandingInformation;
+    ContentLaunchResponse response =
+        ContentLauncherManager().proxyLaunchUrlRequest(contentUrlString, displayStringString, brandingInformation);
+    sendResponse("LaunchURL", response, ZCL_LAUNCH_URL_RESPONSE_COMMAND_ID);
+    return true;
+}
diff --git a/examples/tv-app/android/include/content-launcher/ContentLauncherManager.h b/examples/tv-app/android/include/content-launcher/ContentLauncherManager.h
new file mode 100644
index 0000000..285e521
--- /dev/null
+++ b/examples/tv-app/android/include/content-launcher/ContentLauncherManager.h
@@ -0,0 +1,44 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app-common/zap-generated/af-structs.h>
+#include <app/AttributeAccessInterface.h>
+
+#include <lib/core/CHIPError.h>
+#include <list>
+#include <string>
+#include <vector>
+struct ContentLaunchResponse
+{
+    EmberAfContentLaunchStatus status;
+    std::string data;
+};
+
+class ContentLauncherManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetAcceptsHeader(chip::app::AttributeValueEncoder & aEncoder);
+    CHIP_ERROR proxyGetSupportedStreamingTypes(chip::app::AttributeValueEncoder & aEncoder);
+    ContentLaunchResponse proxyLaunchContentRequest(std::list<ContentLaunchParamater> parameterList, bool autoplay,
+                                                    std::string data);
+    ContentLaunchResponse proxyLaunchUrlRequest(std::string contentUrl, std::string displayString,
+                                                ContentLaunchBrandingInformation brandingInformation);
+};
diff --git a/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.cpp b/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.cpp
new file mode 100644
index 0000000..af4eb31
--- /dev/null
+++ b/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.cpp
@@ -0,0 +1,75 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+#include "EndpointConfigurationStorage.h"
+#include <fstream>
+#include <lib/support/CodeUtils.h>
+
+constexpr const char kEndpointConfigurationPath[] = "/tmp/chip_tv_config.ini";
+
+CHIP_ERROR EndpointConfigurationStorage::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    std::ifstream ifs;
+    ifs.open(kEndpointConfigurationPath, std::ifstream::in);
+    if (!ifs.good())
+    {
+        ifs.open(kEndpointConfigurationPath, std::ifstream::in);
+    }
+    VerifyOrExit(ifs.is_open(), err = CHIP_ERROR_OPEN_FAILED);
+
+    endpointConfig.parse(ifs);
+    ifs.close();
+
+exit:
+    return err;
+}
+
+CHIP_ERROR EndpointConfigurationStorage::get(std::string sectionName, const char * key, char * value, uint16_t & size)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    std::string iniValue;
+    size_t iniValueLength = 0;
+
+    auto section = endpointConfig.sections[sectionName];
+    auto it      = section.find(key);
+    VerifyOrExit(it != section.end(), err = CHIP_ERROR_KEY_NOT_FOUND);
+    VerifyOrExit(inipp::extract(section[key], iniValue), err = CHIP_ERROR_INVALID_ARGUMENT);
+
+    iniValueLength = iniValue.size();
+    VerifyOrExit(iniValueLength <= static_cast<size_t>(size) - 1, err = CHIP_ERROR_BUFFER_TOO_SMALL);
+
+    iniValueLength        = iniValue.copy(value, iniValueLength);
+    value[iniValueLength] = '\0';
+
+exit:
+    return err;
+}
+
+CHIP_ERROR EndpointConfigurationStorage::get(std::string sectionName, const char * key, uint16_t & value)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    auto section = endpointConfig.sections[sectionName];
+    auto it      = section.find(key);
+    VerifyOrExit(it != section.end(), err = CHIP_ERROR_KEY_NOT_FOUND);
+    VerifyOrExit(inipp::extract(section[key], value), err = CHIP_ERROR_INVALID_ARGUMENT);
+
+exit:
+    return err;
+}
diff --git a/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.h b/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.h
new file mode 100644
index 0000000..cea787b
--- /dev/null
+++ b/examples/tv-app/android/include/endpoint-configuration/EndpointConfigurationStorage.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <inipp/inipp.h>
+#include <lib/core/CHIPCore.h>
+#include <string>
+
+class EndpointConfigurationStorage
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR get(std::string sectionName, const char * key, char * value, uint16_t & size);
+    CHIP_ERROR get(std::string sectionName, const char * key, uint16_t & value);
+
+    static EndpointConfigurationStorage & GetInstance()
+    {
+        static EndpointConfigurationStorage instance;
+        return instance;
+    }
+
+private:
+    inipp::Ini<char> endpointConfig;
+};
diff --git a/examples/tv-app/android/include/endpoint-configuration/chip_tv_config.ini b/examples/tv-app/android/include/endpoint-configuration/chip_tv_config.ini
new file mode 100644
index 0000000..7cca745
--- /dev/null
+++ b/examples/tv-app/android/include/endpoint-configuration/chip_tv_config.ini
@@ -0,0 +1,42 @@
+[endpoint1]
+type=videoPlayer
+macAddress=00:00:00:00:00
+
+[endpoint2]
+type=speaker
+
+[endpoint3]
+type=app
+vendorName=exampleVendorName1
+vendorId=1
+name=exampleName1
+productId=1
+id=1
+catalogVendorId=1
+
+[endpoint4]
+type=app
+vendorName=exampleVendorName2
+vendorId=2
+name=exampleName2
+productId=2
+id=2
+catalogVendorId=2
+
+[endpoint5]
+type=app
+vendorName=exampleVendorName3
+vendorId=3
+name=exampleName3
+productId=3
+id= 3
+catalogVendorId=3
+
+[endpoint6]
+type=app
+vendorName=exampleVendorName4
+vendorId=4
+name=exampleName4
+productId=4
+id=4
+catalogVendorId=4
\ No newline at end of file
diff --git a/examples/tv-app/android/include/keypad-input/KeypadInputManager.cpp b/examples/tv-app/android/include/keypad-input/KeypadInputManager.cpp
new file mode 100644
index 0000000..58a39ab
--- /dev/null
+++ b/examples/tv-app/android/include/keypad-input/KeypadInputManager.cpp
@@ -0,0 +1,48 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "KeypadInputManager.h"
+
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+CHIP_ERROR KeypadInputManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // TODO: Store feature map once it is supported
+    map<string, bool> featureMap;
+    featureMap["NV"] = true;
+    featureMap["LK"] = true;
+    featureMap["NK"] = true;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+EmberAfKeypadInputStatus keypadInputClusterSendKey(EmberAfKeypadInputCecKeyCode keyCode)
+{
+    // TODO: Insert code here
+    return EMBER_ZCL_KEYPAD_INPUT_STATUS_SUCCESS;
+}
diff --git a/examples/tv-app/android/include/keypad-input/KeypadInputManager.h b/examples/tv-app/android/include/keypad-input/KeypadInputManager.h
new file mode 100644
index 0000000..d55161a
--- /dev/null
+++ b/examples/tv-app/android/include/keypad-input/KeypadInputManager.h
@@ -0,0 +1,30 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app-common/zap-generated/af-structs.h>
+
+#include <lib/core/CHIPError.h>
+#include <list>
+
+class KeypadInputManager
+{
+public:
+    CHIP_ERROR Init();
+};
diff --git a/examples/tv-app/android/include/level-control/LevelControl.cpp b/examples/tv-app/android/include/level-control/LevelControl.cpp
new file mode 100644
index 0000000..2b53d45
--- /dev/null
+++ b/examples/tv-app/android/include/level-control/LevelControl.cpp
@@ -0,0 +1,180 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include <app/util/af.h>
+
+#include <app-common/zap-generated/af-structs.h>
+#include <app-common/zap-generated/attribute-id.h>
+#include <app-common/zap-generated/attribute-type.h>
+#include <app-common/zap-generated/cluster-id.h>
+#include <app-common/zap-generated/command-id.h>
+
+using namespace chip;
+
+#define MAX_LEVEL 99
+#define MIN_LEVEL 1
+
+typedef struct
+{
+    CommandId commandId;
+    uint16_t storedLevel;
+    bool increasing;
+} EmberAfLevelControlState;
+
+static EmberAfLevelControlState stateTable[EMBER_AF_LEVEL_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT];
+
+static EmberAfLevelControlState * getState(EndpointId endpoint)
+{
+    uint8_t ep = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID);
+    return (ep == 0xFF ? NULL : &stateTable[ep]);
+}
+
+static void stepHandler(CommandId commandId, uint8_t stepMode, uint8_t stepSize, uint16_t transitionTimeDs, uint8_t optionMask,
+                        uint8_t optionOverride)
+{
+
+    EndpointId endpoint              = emberAfCurrentEndpoint();
+    EmberAfLevelControlState * state = getState(endpoint);
+    EmberAfStatus status;
+    uint8_t currentLevel;
+
+    status = emberAfReadServerAttribute(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID,
+                                        (uint8_t *) &currentLevel, sizeof(currentLevel));
+
+    if (status != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
+        goto send_default_response;
+    }
+
+    switch (stepMode)
+    {
+    case EMBER_ZCL_STEP_MODE_UP:
+        state->increasing = true;
+        if (MAX_LEVEL >= currentLevel + stepSize)
+        {
+            currentLevel = currentLevel + stepSize;
+        }
+        break;
+    case EMBER_ZCL_STEP_MODE_DOWN:
+        state->increasing = false;
+        if (MIN_LEVEL <= currentLevel - stepSize)
+        {
+            currentLevel = currentLevel - stepSize;
+        }
+        break;
+    default:
+        status = EMBER_ZCL_STATUS_INVALID_FIELD;
+        goto send_default_response;
+    }
+
+    if (currentLevel != state->storedLevel)
+    {
+        int volumeIncrementCount = abs(currentLevel - state->storedLevel);
+        for (int i = 0; i < volumeIncrementCount; ++i)
+        {
+            if (state->increasing)
+            {
+                ChipLogProgress(Zcl, "Volume UP");
+                // TODO: Insert your code here to send volume up command
+            }
+            else
+            {
+                ChipLogProgress(Zcl, "Volume DOWN");
+                // TODO: Insert your code here to send volume down command
+            }
+        }
+        status             = emberAfWriteServerAttribute(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID,
+                                             (uint8_t *) &currentLevel, ZCL_INT8U_ATTRIBUTE_TYPE);
+        state->storedLevel = currentLevel;
+        ChipLogProgress(Zcl, "Setting volume to new level %d", state->storedLevel);
+    }
+
+send_default_response:
+    if (emberAfCurrentCommand()->apsFrame->clusterId == ZCL_LEVEL_CONTROL_CLUSTER_ID)
+    {
+        emberAfSendImmediateDefaultResponse(status);
+    }
+}
+
+bool emberAfLevelControlClusterStepCallback(uint8_t stepMode, uint8_t stepSize, uint16_t transitionTime, uint8_t optionMask,
+                                            uint8_t optionOverride)
+{
+    stepHandler(ZCL_STEP_COMMAND_ID, stepMode, stepSize, transitionTime, optionMask, optionOverride);
+    return true;
+}
+
+bool emberAfLevelControlClusterMoveCallback(unsigned char, unsigned char, unsigned char, unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterMoveToLevelCallback(unsigned char, unsigned short, unsigned char, unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterMoveToLevelWithOnOffCallback(unsigned char, unsigned short)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterMoveWithOnOffCallback(unsigned char, unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterStopCallback(unsigned char, unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterStopWithOnOffCallback()
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfOnOffClusterLevelControlEffectCallback(unsigned char, bool)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterServerInitCallback(unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterStepWithOnOffCallback(unsigned char, unsigned char, unsigned short)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
+
+bool emberAfLevelControlClusterServerTickCallback(unsigned char)
+{
+    ChipLogProgress(Zcl, "Not supported");
+    return true;
+}
diff --git a/examples/tv-app/android/include/low-power/LowPowerManager.cpp b/examples/tv-app/android/include/low-power/LowPowerManager.cpp
new file mode 100644
index 0000000..97016d4
--- /dev/null
+++ b/examples/tv-app/android/include/low-power/LowPowerManager.cpp
@@ -0,0 +1,25 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "LowPowerManager.h"
+
+bool lowPowerClusterSleep()
+{
+    // TODO: Insert code here
+    return true;
+}
diff --git a/examples/tv-app/android/include/low-power/LowPowerManager.h b/examples/tv-app/android/include/low-power/LowPowerManager.h
new file mode 100644
index 0000000..75d500c
--- /dev/null
+++ b/examples/tv-app/android/include/low-power/LowPowerManager.h
@@ -0,0 +1,26 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <lib/core/CHIPError.h>
+
+class LowPowerManager
+{
+public:
+};
diff --git a/examples/tv-app/android/include/media-input/MediaInputManager.cpp b/examples/tv-app/android/include/media-input/MediaInputManager.cpp
new file mode 100644
index 0000000..bb8dbf2
--- /dev/null
+++ b/examples/tv-app/android/include/media-input/MediaInputManager.cpp
@@ -0,0 +1,80 @@
+/**
+ *
+ *    Copyright (c) 2021 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 "MediaInputManager.h"
+
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/util/af.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
+#include <map>
+#include <string>
+
+CHIP_ERROR MediaInputManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // TODO: Store feature map once it is supported
+    std::map<std::string, bool> featureMap;
+    featureMap["NU"] = true;
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+CHIP_ERROR MediaInputManager::proxyGetInputList(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        int maximumVectorSize = 2;
+        char description[]    = "exampleDescription";
+        char name[]           = "exampleName";
+
+        for (int i = 0; i < maximumVectorSize; ++i)
+        {
+            chip::app::Clusters::MediaInput::Structs::MediaInputInfo::Type mediaInput;
+            mediaInput.description = chip::CharSpan(description, sizeof(description) - 1);
+            mediaInput.name        = chip::CharSpan(name, sizeof(name) - 1);
+            mediaInput.inputType   = EMBER_ZCL_MEDIA_INPUT_TYPE_HDMI;
+            mediaInput.index       = static_cast<uint8_t>(1 + i);
+            ReturnErrorOnFailure(encoder.Encode(mediaInput));
+        }
+
+        return CHIP_NO_ERROR;
+    });
+}
+
+bool mediaInputClusterSelectInput(uint8_t input)
+{
+    // TODO: Insert code here
+    return true;
+}
+bool mediaInputClusterShowInputStatus()
+{
+    // TODO: Insert code here
+    return true;
+}
+bool mediaInputClusterHideInputStatus()
+{
+    // TODO: Insert code here
+    return true;
+}
+bool mediaInputClusterRenameInput(uint8_t input, std::string name)
+{
+    // TODO: Insert code here
+    return true;
+}
diff --git a/examples/tv-app/android/include/media-input/MediaInputManager.h b/examples/tv-app/android/include/media-input/MediaInputManager.h
new file mode 100644
index 0000000..6a88c3b
--- /dev/null
+++ b/examples/tv-app/android/include/media-input/MediaInputManager.h
@@ -0,0 +1,32 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/AttributeAccessInterface.h>
+
+#include <lib/core/CHIPError.h>
+#include <string>
+#include <vector>
+
+class MediaInputManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetInputList(chip::app::AttributeValueEncoder & aEncoder);
+};
diff --git a/examples/tv-app/android/include/media-playback/MediaPlaybackManager.cpp b/examples/tv-app/android/include/media-playback/MediaPlaybackManager.cpp
new file mode 100644
index 0000000..3146f0c
--- /dev/null
+++ b/examples/tv-app/android/include/media-playback/MediaPlaybackManager.cpp
@@ -0,0 +1,79 @@
+/**
+ *
+ *    Copyright (c) 2021 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 "MediaPlaybackManager.h"
+#include <app/Command.h>
+#include <app/util/af.h>
+#include <iostream>
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+CHIP_ERROR MediaPlaybackManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // TODO: Store feature map once it is supported
+    map<string, bool> featureMap;
+    featureMap["AS"] = true;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+EmberAfMediaPlaybackStatus MediaPlaybackManager::proxyMediaPlaybackRequest(MediaPlaybackRequest mediaPlaybackRequest,
+                                                                           uint64_t deltaPositionMilliseconds)
+{
+    switch (mediaPlaybackRequest)
+    {
+    case MEDIA_PLAYBACK_REQUEST_PLAY:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_PAUSE:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_STOP:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_START_OVER:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_PREVIOUS:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_NEXT:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_REWIND:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_FAST_FORWARD:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_SKIP_FORWARD:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_SKIP_BACKWARD:
+    // TODO: Insert code here
+    case MEDIA_PLAYBACK_REQUEST_SEEK:
+        return EMBER_ZCL_MEDIA_PLAYBACK_STATUS_SUCCESS;
+        break;
+    default: {
+        return EMBER_ZCL_MEDIA_PLAYBACK_STATUS_SUCCESS;
+    }
+    }
+}
+
+EmberAfMediaPlaybackStatus mediaPlaybackClusterSendMediaPlaybackRequest(MediaPlaybackRequest mediaPlaybackRequest,
+                                                                        uint64_t deltaPositionMilliseconds)
+{
+    return MediaPlaybackManager().proxyMediaPlaybackRequest(mediaPlaybackRequest, deltaPositionMilliseconds);
+}
diff --git a/examples/tv-app/android/include/media-playback/MediaPlaybackManager.h b/examples/tv-app/android/include/media-playback/MediaPlaybackManager.h
new file mode 100644
index 0000000..e90601e
--- /dev/null
+++ b/examples/tv-app/android/include/media-playback/MediaPlaybackManager.h
@@ -0,0 +1,37 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app-common/zap-generated/enums.h>
+#include <app/clusters/media-playback-server/media-playback-server.h>
+#include <app/util/af-types.h>
+
+#include <lib/core/CHIPError.h>
+
+class MediaPlaybackManager
+{
+public:
+    CHIP_ERROR Init();
+    void storeNewPlaybackState(chip::EndpointId endpoint, uint8_t newPlaybackState);
+    EmberAfMediaPlaybackStatus proxyMediaPlaybackRequest(MediaPlaybackRequest mediaPlaybackRequest,
+                                                         uint64_t deltaPositionMilliseconds);
+
+private:
+    uint8_t oldPlaybackState;
+};
diff --git a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp
new file mode 100644
index 0000000..bb15476
--- /dev/null
+++ b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp
@@ -0,0 +1,66 @@
+/**
+ *
+ *    Copyright (c) 2021 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 "TargetNavigatorManager.h"
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/clusters/target-navigator-server/target-navigator-server.h>
+#include <app/util/af.h>
+#include <app/util/basic-types.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+CHIP_ERROR TargetNavigatorManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+CHIP_ERROR TargetNavigatorManager::proxyGetTargetInfoList(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        int maximumVectorSize = 2;
+        char name[]           = "exampleName";
+
+        for (int i = 0; i < maximumVectorSize; ++i)
+        {
+            chip::app::Clusters::TargetNavigator::Structs::NavigateTargetTargetInfo::Type targetInfo;
+            targetInfo.name       = chip::CharSpan(name, sizeof(name) - 1);
+            targetInfo.identifier = static_cast<uint8_t>(1 + i);
+            ReturnErrorOnFailure(encoder.Encode(targetInfo));
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+TargetNavigatorResponse targetNavigatorClusterNavigateTarget(uint8_t target, std::string data)
+{
+    // TODO: Insert code here
+    TargetNavigatorResponse response;
+    const char * testData = "data response";
+    response.data         = (uint8_t *) testData;
+    response.status       = EMBER_ZCL_APPLICATION_LAUNCHER_STATUS_SUCCESS;
+    return response;
+}
diff --git a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h
new file mode 100644
index 0000000..2aa6046
--- /dev/null
+++ b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h
@@ -0,0 +1,31 @@
+/**
+ *
+ *    Copyright (c) 2021 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/AttributeAccessInterface.h>
+
+#include <lib/core/CHIPError.h>
+#include <string>
+#include <vector>
+
+class TargetNavigatorManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetTargetInfoList(chip::app::AttributeValueEncoder & aEncoder);
+};
diff --git a/examples/tv-app/android/include/tv-channel/TvChannelManager.cpp b/examples/tv-app/android/include/tv-channel/TvChannelManager.cpp
new file mode 100644
index 0000000..5d5a1ea
--- /dev/null
+++ b/examples/tv-app/android/include/tv-channel/TvChannelManager.cpp
@@ -0,0 +1,84 @@
+/**
+ *
+ *    Copyright (c) 2021 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 "TvChannelManager.h"
+#include <app-common/zap-generated/af-structs.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/util/af.h>
+#include <app/util/attribute-storage.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace chip;
+
+CHIP_ERROR TvChannelManager::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    // TODO: Store feature map once it is supported
+    std::map<std::string, bool> featureMap;
+    featureMap["CL"] = true;
+    featureMap["LI"] = true;
+
+    SuccessOrExit(err);
+exit:
+    return err;
+}
+
+CHIP_ERROR TvChannelManager::proxyGetTvChannelList(chip::app::AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([](const chip::app::TagBoundEncoder & encoder) -> CHIP_ERROR {
+        // TODO: Insert code here
+        int maximumVectorSize    = 2;
+        char affiliateCallSign[] = "exampleASign";
+        char callSign[]          = "exampleCSign";
+        char name[]              = "exampleName";
+
+        for (int i = 0; i < maximumVectorSize; ++i)
+        {
+            chip::app::Clusters::TvChannel::Structs::TvChannelInfo::Type channelInfo;
+            channelInfo.affiliateCallSign = CharSpan(affiliateCallSign, sizeof(affiliateCallSign) - 1);
+            channelInfo.callSign          = CharSpan(callSign, sizeof(callSign) - 1);
+            channelInfo.name              = CharSpan(name, sizeof(name) - 1);
+            channelInfo.majorNumber       = static_cast<uint8_t>(1 + i);
+            channelInfo.minorNumber       = static_cast<uint16_t>(2 + i);
+            ReturnErrorOnFailure(encoder.Encode(channelInfo));
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+TvChannelInfo tvChannelClusterChangeChannel(std::string match)
+{
+    // TODO: Insert code here
+    TvChannelInfo channel = {};
+    return channel;
+}
+bool tvChannelClusterChangeChannelByNumber(uint16_t majorNumber, uint16_t minorNumber)
+{
+    // TODO: Insert code here
+    return true;
+}
+bool tvChannelClusterSkipChannel(uint16_t count)
+{
+    // TODO: Insert code here
+    return true;
+}
diff --git a/examples/tv-app/android/include/tv-channel/TvChannelManager.h b/examples/tv-app/android/include/tv-channel/TvChannelManager.h
new file mode 100644
index 0000000..aaf78b8
--- /dev/null
+++ b/examples/tv-app/android/include/tv-channel/TvChannelManager.h
@@ -0,0 +1,31 @@
+/**
+ *
+ *    Copyright (c) 2021 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/AttributeAccessInterface.h>
+
+#include <lib/core/CHIPError.h>
+#include <string>
+#include <vector>
+
+class TvChannelManager
+{
+public:
+    CHIP_ERROR Init();
+    CHIP_ERROR proxyGetTvChannelList(chip::app::AttributeValueEncoder & aEncoder);
+};
diff --git a/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.cpp b/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.cpp
new file mode 100644
index 0000000..3072b0e
--- /dev/null
+++ b/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.cpp
@@ -0,0 +1,73 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "WakeOnLanManager.h"
+
+#include <app-common/zap-generated/attribute-type.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/util/af.h>
+#include <app/util/attribute-storage.h>
+#include <app/util/basic-types.h>
+#include <lib/support/ZclString.h>
+
+#include <inipp/inipp.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+using namespace chip;
+using namespace chip::app::Clusters;
+
+CHIP_ERROR WakeOnLanManager::Init()
+{
+    CHIP_ERROR err                                       = CHIP_NO_ERROR;
+    EndpointConfigurationStorage & endpointConfiguration = EndpointConfigurationStorage::GetInstance();
+    err                                                  = endpointConfiguration.Init();
+    SuccessOrExit(err);
+    es = &endpointConfiguration;
+exit:
+    return err;
+}
+
+void WakeOnLanManager::store(chip::EndpointId endpoint, char macAddress[32])
+{
+    uint8_t bufferMemory[32];
+    MutableByteSpan zclString(bufferMemory);
+    MakeZclCharString(zclString, macAddress);
+    EmberAfStatus macAddressStatus = emberAfWriteServerAttribute(
+        endpoint, WakeOnLan::Id, WakeOnLan::Attributes::WakeOnLanMacAddress::Id, zclString.data(), ZCL_CHAR_STRING_ATTRIBUTE_TYPE);
+    if (macAddressStatus != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ChipLogError(Zcl, "Failed to store mac address attribute.");
+    }
+}
+
+void WakeOnLanManager::setMacAddress(chip::EndpointId endpoint, char * macAddress)
+{
+    char address[18];
+    uint16_t size = static_cast<uint16_t>(sizeof(address));
+
+    std::string section = "endpoint" + std::to_string(endpoint);
+    CHIP_ERROR err      = es->get(section, "macAddress", macAddress, size);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "Failed to get mac address. Error:%s", chip::ErrorStr(err));
+    }
+}
diff --git a/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.h b/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.h
new file mode 100644
index 0000000..2f0cca3
--- /dev/null
+++ b/examples/tv-app/android/include/wake-on-lan/WakeOnLanManager.h
@@ -0,0 +1,41 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <app/util/af-types.h>
+#include <lib/core/CHIPError.h>
+
+#include "../endpoint-configuration/EndpointConfigurationStorage.h"
+
+class WakeOnLanManager
+{
+public:
+    CHIP_ERROR Init();
+    void store(chip::EndpointId endpoint, char macAddress[32]);
+    void setMacAddress(chip::EndpointId endpoint, char * macAddress);
+
+    static WakeOnLanManager & GetInstance()
+    {
+        static WakeOnLanManager instance;
+        return instance;
+    }
+
+private:
+    EndpointConfigurationStorage * es = nullptr;
+};
diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py
index c11fe42..d839e7c 100644
--- a/scripts/build/build/targets.py
+++ b/scripts/build/build/targets.py
@@ -190,6 +190,7 @@
     yield target.Extend('androidstudio-arm64-chip-tool', board=AndroidBoard.AndroidStudio_ARM64, app=AndroidApp.CHIP_TOOL)
     yield target.Extend('androidstudio-x86-chip-tool', board=AndroidBoard.AndroidStudio_X86, app=AndroidApp.CHIP_TOOL)
     yield target.Extend('androidstudio-x64-chip-tool', board=AndroidBoard.AndroidStudio_X64, app=AndroidApp.CHIP_TOOL)
+    yield target.Extend('arm64-chip-tvserver', board=AndroidBoard.ARM64, app=AndroidApp.CHIP_TVServer)
 
 
 def MbedTargets():
diff --git a/scripts/build/builders/android.py b/scripts/build/builders/android.py
index cc2901b..74d47e4 100644
--- a/scripts/build/builders/android.py
+++ b/scripts/build/builders/android.py
@@ -65,15 +65,24 @@
 class AndroidApp(Enum):
     CHIP_TOOL = auto()
     CHIP_TEST = auto()
+    CHIP_TVServer = auto()
 
     def AppName(self):
         if self == AndroidApp.CHIP_TOOL:
             return "CHIPTool"
         elif self == AndroidApp.CHIP_TEST:
             return "CHIPTest"
+        elif self == AndroidApp.CHIP_TVServer:
+            return "CHIPTVServer"
         else:
             raise Exception('Unknown app type: %r' % self)
 
+    def AppGnArgs(self):
+        gn_args = {}
+        if self == AndroidApp.CHIP_TVServer:
+            gn_args['chip_config_network_layer_ble'] = False
+        return gn_args
+
 
 class AndroidBuilder(Builder):
 
@@ -130,12 +139,19 @@
             gn_args['target_cpu'] = self.board.TargetCpuName()
             gn_args['android_ndk_root'] = os.environ['ANDROID_NDK_HOME']
             gn_args['android_sdk_root'] = os.environ['ANDROID_HOME']
-            gn_args['chip_use_clusters_for_ip_commissioning'] = 'true'
+            gn_args['chip_use_clusters_for_ip_commissioning'] = True
+            gn_args.update(self.app.AppGnArgs())
 
-            args = '--args=%s' % (' '.join([
-                '%s="%s"' % (key, shlex.quote(value))
-                for key, value in gn_args.items()
-            ]))
+            args_str = ""
+            for key, value in gn_args.items():
+                if type(value) == bool:
+                    if value:
+                        args_str += '%s=true ' % (key)
+                    else:
+                        args_str += '%s=false ' % (key)
+                else:
+                    args_str += '%s="%s" ' % (key, shlex.quote(value))
+            args = '--args=%s' % (args_str)
 
             gn_gen = [
                 'gn', 'gen', '--check', '--fail-on-unused-args', self.output_dir, args,
@@ -193,16 +209,17 @@
             #
             #   If we unify the JNI libraries, libc++_shared.so may not be needed anymore, which could
             # be another path of resolving this inconsistency.
-            for libName in ['libSetupPayloadParser.so', 'libCHIPController.so', 'libc++_shared.so']:
+            for libName in ['libSetupPayloadParser.so', 'libCHIPController.so', 'libc++_shared.so', 'libCHIPAppServer.so']:
                 self._Execute(['cp', os.path.join(self.output_dir, 'lib', 'jni', self.board.AbiName(
                 ), libName), os.path.join(jnilibs_dir, libName)])
 
             jars = {
                 'CHIPController.jar': 'src/controller/java/CHIPController.jar',
                 'SetupPayloadParser.jar': 'src/setup_payload/java/SetupPayloadParser.jar',
-                'AndroidPlatform.jar': 'src/platform/android/AndroidPlatform.jar'
-
+                'AndroidPlatform.jar': 'src/platform/android/AndroidPlatform.jar',
+                'CHIPAppServer.jar': 'src/app/server/java/CHIPAppServer.jar',
             }
+
             for jarName in jars.keys():
                 self._Execute(['cp', os.path.join(
                     self.output_dir, 'lib', jars[jarName]), os.path.join(libs_dir, jarName)])
diff --git a/scripts/build/testdata/all_targets_except_host.txt b/scripts/build/testdata/all_targets_except_host.txt
index 03c247d..275483b 100644
--- a/scripts/build/testdata/all_targets_except_host.txt
+++ b/scripts/build/testdata/all_targets_except_host.txt
@@ -6,6 +6,7 @@
 android-arm-chip-tool
 android-arm64-chip-test
 android-arm64-chip-tool
+android-arm64-chip-tvserver
 android-x64-chip-tool
 android-x86-chip-tool
 efr32-brd4161a-light
diff --git a/scripts/build/testdata/build_all_except_host.txt b/scripts/build/testdata/build_all_except_host.txt
index 3f785fb..182bc63 100644
--- a/scripts/build/testdata/build_all_except_host.txt
+++ b/scripts/build/testdata/build_all_except_host.txt
@@ -11,7 +11,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-androidstudio-arm-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-androidstudio-arm-chip-tool '--args=target_os="android" target_cpu="arm" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
+gn gen --check --fail-on-unused-args {out}/android-androidstudio-arm-chip-tool '--args=target_os="android" target_cpu="arm" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true ' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -23,7 +23,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-androidstudio-arm64-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-androidstudio-arm64-chip-tool '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
+gn gen --check --fail-on-unused-args {out}/android-androidstudio-arm64-chip-tool '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true ' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -35,7 +35,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-androidstudio-x64-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-androidstudio-x64-chip-tool '--args=target_os="android" target_cpu="x64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
+gn gen --check --fail-on-unused-args {out}/android-androidstudio-x64-chip-tool '--args=target_os="android" target_cpu="x64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true ' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -47,7 +47,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-androidstudio-x86-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-androidstudio-x86-chip-tool '--args=target_os="android" target_cpu="x86" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
+gn gen --check --fail-on-unused-args {out}/android-androidstudio-x86-chip-tool '--args=target_os="android" target_cpu="x86" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true ' --ide=json --json-ide-script=//scripts/examples/gn_to_cmakelists.py
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -59,7 +59,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-arm-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-arm-chip-tool '--args=target_os="android" target_cpu="arm" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"'
+gn gen --check --fail-on-unused-args {out}/android-arm-chip-tool '--args=target_os="android" target_cpu="arm" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true '
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -71,7 +71,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-arm64-chip-test
-gn gen --check --fail-on-unused-args {out}/android-arm64-chip-test '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"'
+gn gen --check --fail-on-unused-args {out}/android-arm64-chip-test '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true '
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -83,7 +83,19 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-arm64-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-arm64-chip-tool '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"'
+gn gen --check --fail-on-unused-args {out}/android-arm64-chip-tool '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true '
+
+# Accepting NDK licenses
+bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
+
+# Generating JARs for Java build rules test
+python3 build/chip/java/tests/generate_jars_for_test.py
+
+# Setting up Android deps through Gradle
+python3 third_party/android_deps/set_up_android_deps.py
+
+# Generating android-arm64-chip-tvserver
+gn gen --check --fail-on-unused-args {out}/android-arm64-chip-tvserver '--args=target_os="android" target_cpu="arm64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true chip_config_network_layer_ble=false '
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -95,7 +107,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-x64-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-x64-chip-tool '--args=target_os="android" target_cpu="x64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"'
+gn gen --check --fail-on-unused-args {out}/android-x64-chip-tool '--args=target_os="android" target_cpu="x64" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true '
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -107,7 +119,7 @@
 python3 third_party/android_deps/set_up_android_deps.py
 
 # Generating android-x86-chip-tool
-gn gen --check --fail-on-unused-args {out}/android-x86-chip-tool '--args=target_os="android" target_cpu="x86" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning="true"'
+gn gen --check --fail-on-unused-args {out}/android-x86-chip-tool '--args=target_os="android" target_cpu="x86" android_ndk_root="TEST_ANDROID_NDK_HOME" android_sdk_root="TEST_ANDROID_HOME" chip_use_clusters_for_ip_commissioning=true '
 
 # Accepting NDK licenses
 bash -c 'yes | TEST_ANDROID_HOME/tools/bin/sdkmanager --licenses >/dev/null'
@@ -444,12 +456,16 @@
 
 cp {out}/android-arm-chip-tool/lib/jni/armeabi-v7a/libc++_shared.so {root}/src/android/CHIPTool/app/libs/jniLibs/armeabi-v7a/libc++_shared.so
 
+cp {out}/android-arm-chip-tool/lib/jni/armeabi-v7a/libCHIPAppServer.so {root}/src/android/CHIPTool/app/libs/jniLibs/armeabi-v7a/libCHIPAppServer.so
+
 cp {out}/android-arm-chip-tool/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTool/app/libs/CHIPController.jar
 
 cp {out}/android-arm-chip-tool/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTool/app/libs/SetupPayloadParser.jar
 
 cp {out}/android-arm-chip-tool/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTool/app/libs/AndroidPlatform.jar
 
+cp {out}/android-arm-chip-tool/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTool/app/libs/CHIPAppServer.jar
+
 # Building APP android-arm-chip-tool
 {root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PmatterBuildSrcDir={out}/android-arm-chip-tool -PmatterSdkSourceBuild=false -PbuildDir={out}/android-arm-chip-tool assembleDebug
 
@@ -465,12 +481,16 @@
 
 cp {out}/android-arm64-chip-test/lib/jni/arm64-v8a/libc++_shared.so {root}/src/android/CHIPTest/app/libs/jniLibs/arm64-v8a/libc++_shared.so
 
+cp {out}/android-arm64-chip-test/lib/jni/arm64-v8a/libCHIPAppServer.so {root}/src/android/CHIPTest/app/libs/jniLibs/arm64-v8a/libCHIPAppServer.so
+
 cp {out}/android-arm64-chip-test/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTest/app/libs/CHIPController.jar
 
 cp {out}/android-arm64-chip-test/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTest/app/libs/SetupPayloadParser.jar
 
 cp {out}/android-arm64-chip-test/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTest/app/libs/AndroidPlatform.jar
 
+cp {out}/android-arm64-chip-test/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTest/app/libs/CHIPAppServer.jar
+
 # Building APP android-arm64-chip-test
 {root}/src/android/CHIPTest/gradlew -p {root}/src/android/CHIPTest -PmatterBuildSrcDir={out}/android-arm64-chip-test -PmatterSdkSourceBuild=false -PbuildDir={out}/android-arm64-chip-test assembleDebug
 
@@ -486,15 +506,44 @@
 
 cp {out}/android-arm64-chip-tool/lib/jni/arm64-v8a/libc++_shared.so {root}/src/android/CHIPTool/app/libs/jniLibs/arm64-v8a/libc++_shared.so
 
+cp {out}/android-arm64-chip-tool/lib/jni/arm64-v8a/libCHIPAppServer.so {root}/src/android/CHIPTool/app/libs/jniLibs/arm64-v8a/libCHIPAppServer.so
+
 cp {out}/android-arm64-chip-tool/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTool/app/libs/CHIPController.jar
 
 cp {out}/android-arm64-chip-tool/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTool/app/libs/SetupPayloadParser.jar
 
 cp {out}/android-arm64-chip-tool/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTool/app/libs/AndroidPlatform.jar
 
+cp {out}/android-arm64-chip-tool/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTool/app/libs/CHIPAppServer.jar
+
 # Building APP android-arm64-chip-tool
 {root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PmatterBuildSrcDir={out}/android-arm64-chip-tool -PmatterSdkSourceBuild=false -PbuildDir={out}/android-arm64-chip-tool assembleDebug
 
+# Building JNI android-arm64-chip-tvserver
+ninja -C {out}/android-arm64-chip-tvserver
+
+# Prepare Native libs android-arm64-chip-tvserver
+mkdir -p {root}/src/android/CHIPTVServer/app/libs/jniLibs/arm64-v8a
+
+cp {out}/android-arm64-chip-tvserver/lib/jni/arm64-v8a/libSetupPayloadParser.so {root}/src/android/CHIPTVServer/app/libs/jniLibs/arm64-v8a/libSetupPayloadParser.so
+
+cp {out}/android-arm64-chip-tvserver/lib/jni/arm64-v8a/libCHIPController.so {root}/src/android/CHIPTVServer/app/libs/jniLibs/arm64-v8a/libCHIPController.so
+
+cp {out}/android-arm64-chip-tvserver/lib/jni/arm64-v8a/libc++_shared.so {root}/src/android/CHIPTVServer/app/libs/jniLibs/arm64-v8a/libc++_shared.so
+
+cp {out}/android-arm64-chip-tvserver/lib/jni/arm64-v8a/libCHIPAppServer.so {root}/src/android/CHIPTVServer/app/libs/jniLibs/arm64-v8a/libCHIPAppServer.so
+
+cp {out}/android-arm64-chip-tvserver/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTVServer/app/libs/CHIPController.jar
+
+cp {out}/android-arm64-chip-tvserver/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTVServer/app/libs/SetupPayloadParser.jar
+
+cp {out}/android-arm64-chip-tvserver/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTVServer/app/libs/AndroidPlatform.jar
+
+cp {out}/android-arm64-chip-tvserver/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTVServer/app/libs/CHIPAppServer.jar
+
+# Building APP android-arm64-chip-tvserver
+{root}/src/android/CHIPTVServer/gradlew -p {root}/src/android/CHIPTVServer -PmatterBuildSrcDir={out}/android-arm64-chip-tvserver -PmatterSdkSourceBuild=false -PbuildDir={out}/android-arm64-chip-tvserver assembleDebug
+
 # Building JNI android-x64-chip-tool
 ninja -C {out}/android-x64-chip-tool
 
@@ -507,12 +556,16 @@
 
 cp {out}/android-x64-chip-tool/lib/jni/x86_64/libc++_shared.so {root}/src/android/CHIPTool/app/libs/jniLibs/x86_64/libc++_shared.so
 
+cp {out}/android-x64-chip-tool/lib/jni/x86_64/libCHIPAppServer.so {root}/src/android/CHIPTool/app/libs/jniLibs/x86_64/libCHIPAppServer.so
+
 cp {out}/android-x64-chip-tool/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTool/app/libs/CHIPController.jar
 
 cp {out}/android-x64-chip-tool/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTool/app/libs/SetupPayloadParser.jar
 
 cp {out}/android-x64-chip-tool/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTool/app/libs/AndroidPlatform.jar
 
+cp {out}/android-x64-chip-tool/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTool/app/libs/CHIPAppServer.jar
+
 # Building APP android-x64-chip-tool
 {root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PmatterBuildSrcDir={out}/android-x64-chip-tool -PmatterSdkSourceBuild=false -PbuildDir={out}/android-x64-chip-tool assembleDebug
 
@@ -528,12 +581,16 @@
 
 cp {out}/android-x86-chip-tool/lib/jni/x86/libc++_shared.so {root}/src/android/CHIPTool/app/libs/jniLibs/x86/libc++_shared.so
 
+cp {out}/android-x86-chip-tool/lib/jni/x86/libCHIPAppServer.so {root}/src/android/CHIPTool/app/libs/jniLibs/x86/libCHIPAppServer.so
+
 cp {out}/android-x86-chip-tool/lib/src/controller/java/CHIPController.jar {root}/src/android/CHIPTool/app/libs/CHIPController.jar
 
 cp {out}/android-x86-chip-tool/lib/src/setup_payload/java/SetupPayloadParser.jar {root}/src/android/CHIPTool/app/libs/SetupPayloadParser.jar
 
 cp {out}/android-x86-chip-tool/lib/src/platform/android/AndroidPlatform.jar {root}/src/android/CHIPTool/app/libs/AndroidPlatform.jar
 
+cp {out}/android-x86-chip-tool/lib/src/app/server/java/CHIPAppServer.jar {root}/src/android/CHIPTool/app/libs/CHIPAppServer.jar
+
 # Building APP android-x86-chip-tool
 {root}/src/android/CHIPTool/gradlew -p {root}/src/android/CHIPTool -PmatterBuildSrcDir={out}/android-x86-chip-tool -PmatterSdkSourceBuild=false -PbuildDir={out}/android-x86-chip-tool assembleDebug
 
diff --git a/scripts/build/testdata/glob_star_targets_except_host.txt b/scripts/build/testdata/glob_star_targets_except_host.txt
index a5e973a..de84e94 100644
--- a/scripts/build/testdata/glob_star_targets_except_host.txt
+++ b/scripts/build/testdata/glob_star_targets_except_host.txt
@@ -6,6 +6,7 @@
 android-arm-chip-tool
 android-arm64-chip-test
 android-arm64-chip-tool
+android-arm64-chip-tvserver
 android-x64-chip-tool
 android-x86-chip-tool
 efr32-brd4161a-light
diff --git a/src/android/CHIPTVServer/.gitignore b/src/android/CHIPTVServer/.gitignore
new file mode 100644
index 0000000..d59e52c
--- /dev/null
+++ b/src/android/CHIPTVServer/.gitignore
@@ -0,0 +1,20 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# Shared libs & JAR libs (those libs are copied into source tree for easy Android build).
+*.so
+*.jar
+*.map
diff --git a/src/android/CHIPTVServer/.idea/.gitignore b/src/android/CHIPTVServer/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/src/android/CHIPTVServer/.idea/compiler.xml b/src/android/CHIPTVServer/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/.idea/gradle.xml b/src/android/CHIPTVServer/.idea/gradle.xml
new file mode 100644
index 0000000..526b4c2
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/gradle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/.idea/misc.xml b/src/android/CHIPTVServer/.idea/misc.xml
new file mode 100644
index 0000000..2a4d5b5
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/misc.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/.idea/runConfigurations.xml b/src/android/CHIPTVServer/.idea/runConfigurations.xml
new file mode 100644
index 0000000..797acea
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/runConfigurations.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/.idea/vcs.xml b/src/android/CHIPTVServer/.idea/vcs.xml
new file mode 100644
index 0000000..c2365ab
--- /dev/null
+++ b/src/android/CHIPTVServer/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/.gitignore b/src/android/CHIPTVServer/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/src/android/CHIPTVServer/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/build.gradle b/src/android/CHIPTVServer/app/build.gradle
new file mode 100644
index 0000000..9217e72
--- /dev/null
+++ b/src/android/CHIPTVServer/app/build.gradle
@@ -0,0 +1,56 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdk 30
+
+    defaultConfig {
+        applicationId "com.tcl.chip.chiptvserver"
+        minSdk 24
+        targetSdk 30
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        externalNativeBuild {
+            cmake {
+                targets "default"
+            }
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs/jniLibs']
+        }
+    }
+
+
+
+
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar","*.so"])
+    implementation 'androidx.appcompat:appcompat:1.3.1'
+    implementation 'com.google.android.material:material:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+    implementation files('libs/AndroidPlatform.jar')
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    implementation 'com.google.zxing:core:3.3.0'
+}
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/libs/README.md b/src/android/CHIPTVServer/app/libs/README.md
new file mode 100644
index 0000000..e99f165
--- /dev/null
+++ b/src/android/CHIPTVServer/app/libs/README.md
@@ -0,0 +1,2 @@
+This directory will contain any .jar files required by the CHIPTool demo app for
+Android.
diff --git a/src/android/CHIPTVServer/app/libs/jniLibs/README.md b/src/android/CHIPTVServer/app/libs/jniLibs/README.md
new file mode 100644
index 0000000..c35dd62
--- /dev/null
+++ b/src/android/CHIPTVServer/app/libs/jniLibs/README.md
@@ -0,0 +1,3 @@
+This directory will contain .so files required by CHIPTool demo app for Android.
+The .so files must be organized into folders by the name of the corresponding
+Android architecture for which they are built, eg. arm64-v8a, x86, x86_64, etc.
diff --git a/src/android/CHIPTVServer/app/proguard-rules.pro b/src/android/CHIPTVServer/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/src/android/CHIPTVServer/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/androidTest/java/com/tcl/chip/chiptvserver/ExampleInstrumentedTest.java b/src/android/CHIPTVServer/app/src/androidTest/java/com/tcl/chip/chiptvserver/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..9bd1c27
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/androidTest/java/com/tcl/chip/chiptvserver/ExampleInstrumentedTest.java
@@ -0,0 +1,24 @@
+package com.tcl.chip.chiptvserver;
+
+import static org.junit.Assert.*;
+
+import android.content.Context;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+  @Test
+  public void useAppContext() {
+    // Context of the app under test.
+    Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    assertEquals("com.tcl.chip.chiptvslave", appContext.getPackageName());
+  }
+}
diff --git a/src/android/CHIPTVServer/app/src/main/AndroidManifest.xml b/src/android/CHIPTVServer/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..383830a
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.tcl.chip.chiptvserver">
+
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
+
+    <application
+        android:allowBackup="true"
+        android:extractNativeLibs="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.CHIPTVSlave">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/MainActivity.java b/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/MainActivity.java
new file mode 100644
index 0000000..6fcde19
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/MainActivity.java
@@ -0,0 +1,68 @@
+package com.tcl.chip.chiptvserver;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import chip.appserver.ChipAppServer;
+import chip.platform.AndroidBleManager;
+import chip.platform.AndroidChipPlatform;
+import chip.platform.ChipMdnsCallbackImpl;
+import chip.platform.NsdManagerServiceResolver;
+import chip.platform.PreferencesConfigurationManager;
+import chip.platform.PreferencesKeyValueStoreManager;
+import chip.setuppayload.DiscoveryCapability;
+import chip.setuppayload.SetupPayload;
+import chip.setuppayload.SetupPayloadParser;
+import java.util.HashSet;
+
+public class MainActivity extends AppCompatActivity {
+
+  private ImageView mQrCodeImg;
+  private TextView mQrCodeTxt;
+  private TextView mManualPairingCodeTxt;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+    mQrCodeImg = findViewById(R.id.qrCodeImg);
+    mQrCodeTxt = findViewById(R.id.qrCodeTxt);
+    mManualPairingCodeTxt = findViewById(R.id.manualPairingCodeTxt);
+    ChipAppServer chipAppServer = new ChipAppServer();
+    AndroidChipPlatform chipPlatform =
+        new AndroidChipPlatform(
+            new AndroidBleManager(),
+            new PreferencesKeyValueStoreManager(this),
+            new PreferencesConfigurationManager(this),
+            new NsdManagerServiceResolver(this),
+            new ChipMdnsCallbackImpl());
+
+    // TODO: Get these parameters from PreferencesConfigurationManager
+    HashSet<DiscoveryCapability> discoveryCapabilities = new HashSet<>();
+    discoveryCapabilities.add(DiscoveryCapability.ON_NETWORK);
+    SetupPayload payload =
+        new SetupPayload(0, 9050, 65279, 0, discoveryCapabilities, 3840, 20202021);
+
+    SetupPayloadParser parser = new SetupPayloadParser();
+    try {
+      String qrCode = parser.getQrCodeFromPayload(payload);
+      mQrCodeTxt.setText(qrCode);
+
+      Bitmap qrCodeBitmap = QRUtils.createQRCodeBitmap(qrCode, 800, 800);
+      mQrCodeImg.setImageBitmap(qrCodeBitmap);
+    } catch (SetupPayloadParser.SetupPayloadException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      String manualPairingCode = parser.getManualEntryCodeFromPayload(payload);
+      mManualPairingCodeTxt.setText("ManualPairingCode:" + manualPairingCode);
+    } catch (SetupPayloadParser.SetupPayloadException e) {
+      e.printStackTrace();
+    }
+
+    chipAppServer.startApp();
+  }
+}
diff --git a/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/QRUtils.java b/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/QRUtils.java
new file mode 100644
index 0000000..7a408c2
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/java/com/tcl/chip/chiptvserver/QRUtils.java
@@ -0,0 +1,29 @@
+package com.tcl.chip.chiptvserver;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+
+public class QRUtils {
+  public static Bitmap createQRCodeBitmap(String content, int width, int height) {
+    try {
+      BitMatrix bitMatrix =
+          new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height);
+      Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+      for (int y = 0; y < bitMatrix.getHeight(); y++) {
+        for (int x = 0; x < bitMatrix.getWidth(); x++) {
+          if (bitMatrix.get(x, y)) {
+            bitmap.setPixel(x, y, Color.BLACK);
+          }
+        }
+      }
+      return bitmap;
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+}
diff --git a/src/android/CHIPTVServer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src/android/CHIPTVServer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/drawable/ic_launcher_background.xml b/src/android/CHIPTVServer/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/src/android/CHIPTVServer/app/src/main/res/layout/activity_main.xml b/src/android/CHIPTVServer/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..65b4ee5
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:id="@+id/qrCodeTxt"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        android:layout_marginTop="20dp" />
+
+    <ImageView
+        android:id="@+id/qrCodeImg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@+id/qrCodeTxt"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/manualPairingCodeTxt"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@+id/qrCodeImg"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/src/android/CHIPTVServer/app/src/main/res/values-night/themes.xml b/src/android/CHIPTVServer/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..da91ed1
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CHIPTVSlave" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/values/colors.xml b/src/android/CHIPTVServer/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/values/strings.xml b/src/android/CHIPTVServer/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a2cc2fe
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">CHIPTVServer</string>
+</resources>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/main/res/values/themes.xml b/src/android/CHIPTVServer/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..3e95998
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CHIPTVSlave" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/app/src/test/java/com/tcl/chip/chiptvslave/ExampleUnitTest.java b/src/android/CHIPTVServer/app/src/test/java/com/tcl/chip/chiptvslave/ExampleUnitTest.java
new file mode 100644
index 0000000..6ee6449
--- /dev/null
+++ b/src/android/CHIPTVServer/app/src/test/java/com/tcl/chip/chiptvslave/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.tcl.chip.chiptvserver;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+  @Test
+  public void addition_isCorrect() {
+    assertEquals(4, 2 + 2);
+  }
+}
diff --git a/src/android/CHIPTVServer/build.gradle b/src/android/CHIPTVServer/build.gradle
new file mode 100644
index 0000000..79c656c
--- /dev/null
+++ b/src/android/CHIPTVServer/build.gradle
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.2.2'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+        jcenter() // Warning: this repository is going to shut down soon
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/src/android/CHIPTVServer/gradle.properties b/src/android/CHIPTVServer/gradle.properties
new file mode 100644
index 0000000..e94cae8
--- /dev/null
+++ b/src/android/CHIPTVServer/gradle.properties
@@ -0,0 +1,27 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
+# Build SDK from source code and debug in Android Stduio. Must also set matterBuildSrcDir.
+matterSdkSourceBuild=false
+# Point to the SDK build dir without quotes (out/android-arm64-chip-test for
+# example) to build SDK from source code and debug in Android Studio.
+# Set to blank to use the SDK prebuilt by scripts/build/build_examples.py.
+matterBuildSrcDir=out/android-arm64-chip-tvserver
+
diff --git a/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.jar b/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.properties b/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..da67dc8
--- /dev/null
+++ b/src/android/CHIPTVServer/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Oct 26 11:10:18 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/src/android/CHIPTVServer/gradlew b/src/android/CHIPTVServer/gradlew
new file mode 100755
index 0000000..180cdce
--- /dev/null
+++ b/src/android/CHIPTVServer/gradlew
@@ -0,0 +1,184 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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
+#
+#      https://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.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ]; do
+    ls=$(ls -ld "$PRG")
+    link=$(expr "$ls" : '.*-> \(.*\)$')
+    if expr "$link" : '/.*' >/dev/null; then
+        PRG="$link"
+    else
+        PRG=$(dirname "$PRG")"/$link"
+    fi
+done
+SAVED="$PWD"
+cd "$(dirname "$PRG")" >/dev/null
+APP_HOME="$(pwd -P)"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=$(basename "$0")
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn() {
+    echo "$*"
+}
+
+die() {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$(uname)" in
+    CYGWIN*)
+        cygwin=true
+        ;;
+    Darwin*)
+        darwin=true
+        ;;
+    MINGW*)
+        msys=true
+        ;;
+    NONSTOP*)
+        nonstop=true
+        ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ]; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
+    MAX_FD_LIMIT=$(ulimit -H -n)
+    if [ $? -eq 0 ]; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n "$MAX_FD"
+        if [ $? -ne 0 ]; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if "$darwin"; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ]; then
+    APP_HOME=$(cygpath --path --mixed "$APP_HOME")
+    CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
+
+    JAVACMD=$(cygpath --unix "$JAVACMD")
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
+    SEP=""
+    for dir in "$ROOTDIRSRAW"; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ]; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@"; do
+        CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
+        CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
+
+        if [ "$CHECK" -ne 0 ] && [ "$CHECK2" -eq 0 ]; then ### Added a condition
+            eval "$(echo args"$i")=$(cygpath --path --ignore --mixed "$arg")"
+        else
+            eval "$(echo args"$i")=\"$arg\""
+        fi
+        i=$(expr "$i" + 1)
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save() {
+    for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- "$DEFAULT_JVM_OPTS" "$JAVA_OPTS" "$GRADLE_OPTS" "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/src/android/CHIPTVServer/gradlew.bat b/src/android/CHIPTVServer/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/src/android/CHIPTVServer/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/android/CHIPTVServer/settings.gradle b/src/android/CHIPTVServer/settings.gradle
new file mode 100644
index 0000000..8392989
--- /dev/null
+++ b/src/android/CHIPTVServer/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = "CHIPTVServer"
+include ':app'
diff --git a/src/app/server/java/AppMain.cpp b/src/app/server/java/AppMain.cpp
new file mode 100644
index 0000000..18f1c2c
--- /dev/null
+++ b/src/app/server/java/AppMain.cpp
@@ -0,0 +1,67 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#include "AppMain.h"
+#include <app/server/OnboardingCodesUtil.h>
+#include <app/server/Server.h>
+#include <credentials/DeviceAttestationCredsProvider.h>
+#include <credentials/examples/DeviceAttestationCredsExample.h>
+#include <iostream>
+#include <lib/core/CHIPError.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/JniTypeWrappers.h>
+#include <lib/support/ScopedBuffer.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/PlatformManager.h>
+#include <setup_payload/QRCodeSetupPayloadGenerator.h>
+#include <setup_payload/SetupPayload.h>
+#include <thread>
+
+using namespace chip;
+using namespace chip::Credentials;
+using namespace chip::Inet;
+using namespace chip::Transport;
+using namespace chip::DeviceLayer;
+
+CHIP_ERROR ChipAndroidAppInit(void)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    err = chip::Platform::MemoryInit();
+    SuccessOrExit(err);
+
+    err = chip::DeviceLayer::PlatformMgr().InitChipStack();
+    SuccessOrExit(err);
+
+    ConfigurationMgr().LogDeviceConfig();
+
+    // Init ZCL Data Model and CHIP App Server
+    err = chip::Server::GetInstance().Init(nullptr, CHIP_PORT, CHIP_UDC_PORT);
+    SuccessOrExit(err);
+
+    // TODO: move load DAC to java
+    SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogProgress(NotSpecified, "Failed to run ChipAndroidAppInit: %s ", ErrorStr(err));
+        return err;
+    }
+    return err;
+}
diff --git a/src/app/server/java/AppMain.h b/src/app/server/java/AppMain.h
new file mode 100644
index 0000000..ae54475
--- /dev/null
+++ b/src/app/server/java/AppMain.h
@@ -0,0 +1,22 @@
+/*
+ *
+ *    Copyright (c) 2021 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.
+ */
+
+#pragma once
+#include <lib/core/CHIPError.h>
+
+CHIP_ERROR ChipAndroidAppInit(void);
diff --git a/src/app/server/java/BUILD.gn b/src/app/server/java/BUILD.gn
new file mode 100644
index 0000000..d4170c2
--- /dev/null
+++ b/src/app/server/java/BUILD.gn
@@ -0,0 +1,76 @@
+# Copyright (c) 2021 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.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${build_root}/config/android_abi.gni")
+import("${chip_root}/build/chip/java/rules.gni")
+import("${chip_root}/src/app/common_flags.gni")
+import("${chip_root}/src/lib/lib.gni")
+
+shared_library("jni") {
+  output_name = "libCHIPAppServer"
+
+  sources = [
+    "AppMain.cpp",
+    "AppMain.h",
+    "CHIPAppServer-JNI.cpp",
+  ]
+
+  deps = [
+    "${chip_root}/examples/tv-app/android:android-tv-app",
+    "${chip_root}/examples/tv-app/tv-common",
+    "${chip_root}/src/app/server",
+    "${chip_root}/src/inet",
+    "${chip_root}/src/lib",
+    "${chip_root}/src/platform",
+    "${chip_root}/src/platform/android",
+    "${chip_root}/third_party/inipp",
+  ]
+
+  public_configs = [ "${chip_root}/src:includes" ]
+
+  output_dir = "${root_out_dir}/lib/jni/${android_abi}"
+
+  ldflags = [ "-Wl,--gc-sections" ]
+}
+
+android_library("java") {
+  output_name = "CHIPAppServer.jar"
+
+  deps = [
+    ":android",
+    "${chip_root}/third_party/android_deps:annotation",
+  ]
+
+  data_deps = [
+    ":jni",
+    "${chip_root}/build/chip/java:shared_cpplib",
+  ]
+
+  sources = [
+    "src/chip/appserver/ChipAppServer.java",
+    "src/chip/appserver/ChipAppServerException.java",
+  ]
+
+  javac_flags = [ "-Xlint:deprecation" ]
+
+  # TODO: add classpath support (we likely need to add something like
+  #  ..../platforms/android-21/android.jar to access BLE items)
+}
+
+java_prebuilt("android") {
+  jar_path = "${android_sdk_root}/platforms/android-21/android.jar"
+}
diff --git a/src/app/server/java/CHIPAppServer-JNI.cpp b/src/app/server/java/CHIPAppServer-JNI.cpp
new file mode 100644
index 0000000..777a475
--- /dev/null
+++ b/src/app/server/java/CHIPAppServer-JNI.cpp
@@ -0,0 +1,157 @@
+/*
+ *   Copyright (c) 2021 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.
+ *
+ */
+
+/**
+ *    @file
+ *      Implementation of JNI bridge for CHIP App Server for Android TV apps
+ *
+ */
+#include "AppMain.h"
+#include <jni.h>
+#include <lib/core/CHIPError.h>
+#include <lib/support/CHIPJNIError.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/CodeUtils.h>
+#include <lib/support/JniReferences.h>
+#include <lib/support/JniTypeWrappers.h>
+#include <platform/CHIPDeviceConfig.h>
+#include <platform/ConfigurationManager.h>
+#include <platform/ConnectivityManager.h>
+#include <platform/KeyValueStoreManager.h>
+#include <platform/android/AndroidChipPlatform-JNI.h>
+#include <platform/internal/BLEManager.h>
+
+using namespace chip;
+using namespace chip::DeviceLayer;
+
+#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_chip_appserver_ChipAppServer_##METHOD_NAME
+
+#ifndef PTHREAD_NULL
+#define PTHREAD_NULL 0
+#endif // PTHREAD_NULL
+
+static void * IOThreadAppMain(void * arg);
+
+namespace {
+JavaVM * sJVM;
+pthread_t sIOThread               = PTHREAD_NULL;
+jclass sChipAppServerExceptionCls = NULL;
+} // namespace
+
+jint JNI_OnLoad(JavaVM * jvm, void * reserved)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    JNIEnv * env;
+
+    ChipLogProgress(AppServer, "JNI_OnLoad() called");
+
+    chip::Platform::MemoryInit();
+
+    // Save a reference to the JVM.  Will need this to call back into Java.
+    JniReferences::GetInstance().SetJavaVm(jvm, "chip/appserver/ChipAppServer");
+    sJVM = jvm;
+
+    // Get a JNI environment object.
+    env = JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV);
+
+    ChipLogProgress(AppServer, "Loading Java class references.");
+
+    // Get various class references need by the API.
+    err = JniReferences::GetInstance().GetClassRef(env, "chip/appserver/ChipAppServerException", sChipAppServerExceptionCls);
+    SuccessOrExit(err);
+    ChipLogProgress(AppServer, "Java class references loaded.");
+
+    err = AndroidChipPlatformJNI_OnLoad(jvm, reserved);
+    SuccessOrExit(err);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        JniReferences::GetInstance().ThrowError(env, sChipAppServerExceptionCls, err);
+        chip::DeviceLayer::StackUnlock unlock;
+        JNI_OnUnload(jvm, reserved);
+    }
+
+    return (err == CHIP_NO_ERROR) ? JNI_VERSION_1_6 : JNI_ERR;
+}
+
+void JNI_OnUnload(JavaVM * jvm, void * reserved)
+{
+    chip::DeviceLayer::StackLock lock;
+    ChipLogProgress(AppServer, "JNI_OnUnload() called");
+
+    // If the IO thread has been started, shut it down and wait for it to exit.
+    if (sIOThread != PTHREAD_NULL)
+    {
+        chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
+
+        chip::DeviceLayer::StackUnlock unlock;
+        pthread_join(sIOThread, NULL);
+    }
+
+    sJVM = NULL;
+
+    chip::Platform::MemoryShutdown();
+}
+
+JNI_METHOD(jboolean, startApp)(JNIEnv * env, jobject self)
+{
+    chip::DeviceLayer::StackLock lock;
+
+    CHIP_ERROR err = ChipAndroidAppInit();
+    SuccessOrExit(err);
+
+    if (sIOThread == PTHREAD_NULL)
+    {
+        pthread_create(&sIOThread, NULL, IOThreadAppMain, NULL);
+    }
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+void * IOThreadAppMain(void * arg)
+{
+    JNIEnv * env;
+    JavaVMAttachArgs attachArgs;
+
+    // Attach the IO thread to the JVM as a daemon thread.
+    // This allows the JVM to shutdown without waiting for this thread to exit.
+    attachArgs.version = JNI_VERSION_1_6;
+    attachArgs.name    = (char *) "CHIP AppServer IO Thread";
+    attachArgs.group   = NULL;
+#ifdef __ANDROID__
+    sJVM->AttachCurrentThreadAsDaemon(&env, (void *) &attachArgs);
+#else
+    sJVM->AttachCurrentThreadAsDaemon((void **) &env, (void *) &attachArgs);
+#endif
+
+    ChipLogProgress(AppServer, "IO thread starting");
+    chip::DeviceLayer::PlatformMgr().RunEventLoop();
+    ChipLogProgress(AppServer, "IO thread ending");
+
+    // Detach the thread from the JVM.
+    sJVM->DetachCurrentThread();
+
+    return NULL;
+}
diff --git a/src/app/server/java/src/chip/appserver/ChipAppServer.java b/src/app/server/java/src/chip/appserver/ChipAppServer.java
new file mode 100644
index 0000000..6d9bb3a
--- /dev/null
+++ b/src/app/server/java/src/chip/appserver/ChipAppServer.java
@@ -0,0 +1,29 @@
+/*
+ *   Copyright (c) 2021 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package chip.appserver;
+
+/** Controller to interact with the CHIP device. */
+public class ChipAppServer {
+  private static final String TAG = ChipAppServer.class.getSimpleName();
+
+  static {
+    System.loadLibrary("CHIPAppServer");
+  }
+
+  public native boolean startApp();
+}
diff --git a/src/app/server/java/src/chip/appserver/ChipAppServerException.java b/src/app/server/java/src/chip/appserver/ChipAppServerException.java
new file mode 100644
index 0000000..08e11d1
--- /dev/null
+++ b/src/app/server/java/src/chip/appserver/ChipAppServerException.java
@@ -0,0 +1,14 @@
+package chip.appserver;
+
+public class ChipAppServerException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
+
+  public int errorCode;
+
+  public ChipAppServerException() {}
+
+  public ChipAppServerException(int errorCode, String message) {
+    super(message != null ? message : String.format("Error Code %d", errorCode));
+    this.errorCode = errorCode;
+  }
+}
diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h
index f1f0fc3..408d199 100644
--- a/src/controller/CHIPDevice.h
+++ b/src/controller/CHIPDevice.h
@@ -105,7 +105,7 @@
 typedef void (*OnDeviceConnectionFailure)(void * context, NodeId deviceId, CHIP_ERROR error);
 typedef void (*OnOpenCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload);
 
-class DLL_EXPORT Device : public Messaging::ExchangeDelegate, public SessionEstablishmentDelegate
+class Device : public Messaging::ExchangeDelegate, public SessionEstablishmentDelegate
 {
 public:
     ~Device();
@@ -588,7 +588,7 @@
  * Device when a new message or status update is received from the corresponding
  * CHIP device.
  */
-class DLL_EXPORT DeviceStatusDelegate
+class DeviceStatusDelegate
 {
 public:
     virtual ~DeviceStatusDelegate() {}
diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp
index d293fb0..7a5c78e 100644
--- a/src/controller/java/AndroidDeviceControllerWrapper.cpp
+++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp
@@ -213,7 +213,9 @@
     initParams.inetLayer     = inetLayer;
     initParams.fabricStorage = wrapper.get();
     // move bleLayer into platform/android to share with app server
-    initParams.bleLayer                        = DeviceLayer::ConnectivityMgr().GetBleLayer();
+#if CONFIG_NETWORK_LAYER_BLE
+    initParams.bleLayer = DeviceLayer::ConnectivityMgr().GetBleLayer();
+#endif
     initParams.listenPort                      = CHIP_PORT + 1;
     setupParams.storageDelegate                = wrapper.get();
     setupParams.pairingDelegate                = wrapper.get();
diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn
index 6bd7248..f627e30 100644
--- a/src/controller/java/BUILD.gn
+++ b/src/controller/java/BUILD.gn
@@ -42,6 +42,8 @@
   public_configs = [ "${chip_root}/src:includes" ]
 
   output_dir = "${root_out_dir}/lib/jni/${android_abi}"
+
+  ldflags = [ "-Wl,--gc-sections" ]
 }
 
 android_library("java") {
diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp
index 8540976..4d087be 100644
--- a/src/controller/java/CHIPDeviceController-JNI.cpp
+++ b/src/controller/java/CHIPDeviceController-JNI.cpp
@@ -59,9 +59,7 @@
 
 #define CDC_JNI_CALLBACK_LOCAL_REF_COUNT 256
 
-static void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow);
 static void * IOThreadMain(void * arg);
-static CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx);
 static CHIP_ERROR N2J_PaseVerifierParams(JNIEnv * env, jlong setupPincode, jint passcodeId, jbyteArray pakeVerifier,
                                          jobject & outParams);
 
@@ -112,7 +110,7 @@
 exit:
     if (err != CHIP_NO_ERROR)
     {
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
         chip::DeviceLayer::StackUnlock unlock;
         JNI_OnUnload(jvm, reserved);
     }
@@ -175,7 +173,7 @@
 
         if (err != CHIP_JNI_ERROR_EXCEPTION_THROWN)
         {
-            ThrowError(env, err);
+            JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
         }
     }
 
@@ -193,7 +191,9 @@
 
     RendezvousParameters params = RendezvousParameters()
                                       .SetSetupPINCode(pinCode)
+#if CONFIG_NETWORK_LAYER_BLE
                                       .SetConnectionObject(reinterpret_cast<BLE_CONNECTION_OBJECT>(connObj))
+#endif
                                       .SetPeerAddress(Transport::PeerAddress::BLE());
     if (csrNonce != nullptr)
     {
@@ -205,7 +205,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to pair the device.");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 }
 
@@ -222,7 +222,8 @@
     Inet::IPAddress addr;
     JniUtfString addrJniString(env, address);
     VerifyOrReturn(Inet::IPAddress::FromString(addrJniString.c_str(), addr),
-                   ChipLogError(Controller, "Failed to parse IP address."), ThrowError(env, CHIP_ERROR_INVALID_ARGUMENT));
+                   ChipLogError(Controller, "Failed to parse IP address."),
+                   JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, CHIP_ERROR_INVALID_ARGUMENT));
 
     RendezvousParameters params = RendezvousParameters()
                                       .SetDiscriminator(discriminator)
@@ -238,7 +239,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to pair the device.");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 }
 
@@ -255,7 +256,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to unpair the device.");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 }
 
@@ -272,7 +273,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to unpair the device.");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 }
 
@@ -321,7 +322,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to get device address.");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 
     addr.ToString(addrStr);
@@ -347,7 +348,7 @@
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(Controller, "Failed to update device");
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
 }
 
@@ -451,7 +452,7 @@
 exit:
     if (err != CHIP_NO_ERROR)
     {
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
     }
     return nullptr;
 }
@@ -482,18 +483,6 @@
     return NULL;
 }
 
-void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-    jthrowable ex;
-
-    err = N2J_Error(env, errToThrow, ex);
-    if (err == CHIP_NO_ERROR)
-    {
-        env->Throw(ex);
-    }
-}
-
 CHIP_ERROR N2J_PaseVerifierParams(JNIEnv * env, jlong setupPincode, jint passcodeId, jbyteArray paseVerifier, jobject & outParams)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
@@ -514,43 +503,3 @@
 exit:
     return err;
 }
-
-CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx)
-{
-    CHIP_ERROR err      = CHIP_NO_ERROR;
-    const char * errStr = NULL;
-    jstring errStrObj   = NULL;
-    jmethodID constructor;
-
-    env->ExceptionClear();
-    constructor = env->GetMethodID(sChipDeviceControllerExceptionCls, "<init>", "(ILjava/lang/String;)V");
-    VerifyOrExit(constructor != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND);
-
-    switch (inErr.AsInteger())
-    {
-    case CHIP_JNI_ERROR_TYPE_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI type not found";
-        break;
-    case CHIP_JNI_ERROR_METHOD_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI method not found";
-        break;
-    case CHIP_JNI_ERROR_FIELD_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI field not found";
-        break;
-    case CHIP_JNI_ERROR_DEVICE_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: Device not found";
-        break;
-    default:
-        errStr = ErrorStr(inErr);
-        break;
-    }
-    errStrObj = (errStr != NULL) ? env->NewStringUTF(errStr) : NULL;
-
-    outEx = (jthrowable) env->NewObject(sChipDeviceControllerExceptionCls, constructor, static_cast<jint>(inErr.AsInteger()),
-                                        errStrObj);
-    VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN);
-
-exit:
-    env->DeleteLocalRef(errStrObj);
-    return err;
-}
diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp
index 69156a3..3b5c367 100644
--- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp
+++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.cpp
@@ -56,8 +56,6 @@
 template <class ImplClass>
 CHIP_ERROR GenericPlatformManagerImpl_POSIX<ImplClass>::_InitChipStack()
 {
-    mChipStackLock = PTHREAD_MUTEX_INITIALIZER;
-
     // Call up to the base class _InitChipStack() to perform the bulk of the initialization.
     ReturnErrorOnFailure(GenericPlatformManagerImpl<ImplClass>::_InitChipStack());
 
@@ -104,6 +102,10 @@
 void GenericPlatformManagerImpl_POSIX<ImplClass>::_UnlockChipStack()
 {
 #if CHIP_STACK_LOCK_TRACKING_ENABLED
+    if (!mChipStackIsLocked)
+    {
+        ChipLogError(DeviceLayer, "_UnlockChipStack may error status");
+    }
     mChipStackIsLocked = false;
 #endif
 
diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h
index e2d5fee..a791c67 100644
--- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h
+++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h
@@ -53,7 +53,7 @@
 {
 protected:
     // OS-specific members (pthread)
-    pthread_mutex_t mChipStackLock;
+    pthread_mutex_t mChipStackLock = PTHREAD_MUTEX_INITIALIZER;
 
     enum TaskType
     {
diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp
index d1190fa..a8c43d3 100644
--- a/src/lib/support/JniReferences.cpp
+++ b/src/lib/support/JniReferences.cpp
@@ -15,6 +15,7 @@
  *    limitations under the License.
  */
 
+#include <jni.h>
 #include <lib/support/CHIPJNIError.h>
 #include <lib/support/CodeUtils.h>
 #include <lib/support/JniReferences.h>
@@ -169,4 +170,17 @@
     }
 }
 
+void JniReferences::ThrowError(JNIEnv * env, jclass exceptionCls, CHIP_ERROR errToThrow)
+{
+    env->ExceptionClear();
+    jmethodID constructor = env->GetMethodID(exceptionCls, "<init>", "(ILjava/lang/String;)V");
+    VerifyOrReturn(constructor != NULL);
+
+    jstring jerrStr = env->NewStringUTF(ErrorStr(errToThrow));
+
+    jthrowable outEx = (jthrowable) env->NewObject(exceptionCls, constructor, static_cast<jint>(errToThrow.AsInteger()), jerrStr);
+    VerifyOrReturn(!env->ExceptionCheck());
+    env->Throw(outEx);
+}
+
 } // namespace chip
diff --git a/src/lib/support/JniReferences.h b/src/lib/support/JniReferences.h
index 5e8cbe6..ed0ede5 100644
--- a/src/lib/support/JniReferences.h
+++ b/src/lib/support/JniReferences.h
@@ -78,6 +78,8 @@
 
     void ReportError(JNIEnv * env, CHIP_ERROR cbErr, const char * functName);
 
+    void ThrowError(JNIEnv * env, jclass exceptionCls, CHIP_ERROR errToThrow);
+
 private:
     JniReferences() {}
 
diff --git a/src/platform/android/AndroidChipPlatform-JNI.cpp b/src/platform/android/AndroidChipPlatform-JNI.cpp
index bd1bbca..785ed89 100644
--- a/src/platform/android/AndroidChipPlatform-JNI.cpp
+++ b/src/platform/android/AndroidChipPlatform-JNI.cpp
@@ -43,9 +43,9 @@
 #define JNI_MDNSCALLBACK_METHOD(RETURN, METHOD_NAME)                                                                               \
     extern "C" JNIEXPORT RETURN JNICALL Java_chip_platform_ChipMdnsCallbackImpl_##METHOD_NAME
 
-static void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow);
-static CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx);
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
 static bool JavaBytesToUUID(JNIEnv * env, jbyteArray value, chip::Ble::ChipBleUUID & uuid);
+#endif
 
 namespace {
 JavaVM * sJVM;
@@ -83,7 +83,7 @@
 exit:
     if (err != CHIP_NO_ERROR)
     {
-        ThrowError(env, err);
+        JniReferences::GetInstance().ThrowError(env, sAndroidChipPlatformExceptionCls, err);
         JNI_OnUnload(jvm, reserved);
     }
 
@@ -108,6 +108,7 @@
 JNI_METHOD(void, handleWriteConfirmation)
 (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
 {
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
     chip::DeviceLayer::StackLock lock;
     BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
 
@@ -119,11 +120,13 @@
                    ChipLogError(DeviceLayer, "handleWriteConfirmation() called with invalid characteristic ID"));
 
     chip::DeviceLayer::Internal::BLEMgrImpl().HandleWriteConfirmation(connObj, &svcUUID, &charUUID);
+#endif
 }
 
 JNI_METHOD(void, handleIndicationReceived)
 (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId, jbyteArray value)
 {
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
     chip::DeviceLayer::StackLock lock;
     BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
     const auto valueBegin               = env->GetByteArrayElements(value, nullptr);
@@ -144,11 +147,13 @@
     chip::DeviceLayer::Internal::BLEMgrImpl().HandleIndicationReceived(connObj, &svcUUID, &charUUID, std::move(buffer));
 exit:
     env->ReleaseByteArrayElements(value, valueBegin, 0);
+#endif
 }
 
 JNI_METHOD(void, handleSubscribeComplete)
 (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
 {
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
     chip::DeviceLayer::StackLock lock;
     BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
 
@@ -160,11 +165,13 @@
                    ChipLogError(DeviceLayer, "handleSubscribeComplete() called with invalid characteristic ID"));
 
     chip::DeviceLayer::Internal::BLEMgrImpl().HandleSubscribeComplete(connObj, &svcUUID, &charUUID);
+#endif
 }
 
 JNI_METHOD(void, handleUnsubscribeComplete)
 (JNIEnv * env, jobject self, jint conn, jbyteArray svcId, jbyteArray charId)
 {
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
     chip::DeviceLayer::StackLock lock;
     BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
 
@@ -176,14 +183,17 @@
                    ChipLogError(DeviceLayer, "handleUnsubscribeComplete() called with invalid characteristic ID"));
 
     chip::DeviceLayer::Internal::BLEMgrImpl().HandleUnsubscribeComplete(connObj, &svcUUID, &charUUID);
+#endif
 }
 
 JNI_METHOD(void, handleConnectionError)(JNIEnv * env, jobject self, jint conn)
 {
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
     chip::DeviceLayer::StackLock lock;
     BLE_CONNECTION_OBJECT const connObj = reinterpret_cast<BLE_CONNECTION_OBJECT>(conn);
 
     chip::DeviceLayer::Internal::BLEMgrImpl().HandleConnectionError(connObj, BLE_ERROR_APP_CLOSED_CONNECTION);
+#endif
 }
 
 // for KeyValueStoreManager
@@ -215,58 +225,7 @@
     HandleResolve(instanceName, serviceType, address, port, callbackHandle, contextHandle);
 }
 
-void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow)
-{
-    CHIP_ERROR err = CHIP_NO_ERROR;
-    jthrowable ex;
-
-    err = N2J_Error(env, errToThrow, ex);
-    if (err == CHIP_NO_ERROR)
-    {
-        env->Throw(ex);
-    }
-}
-
-CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx)
-{
-    CHIP_ERROR err      = CHIP_NO_ERROR;
-    const char * errStr = NULL;
-    jstring errStrObj   = NULL;
-    jmethodID constructor;
-
-    env->ExceptionClear();
-    constructor = env->GetMethodID(sAndroidChipPlatformExceptionCls, "<init>", "(ILjava/lang/String;)V");
-    VerifyOrExit(constructor != NULL, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND);
-
-    switch (inErr.AsInteger())
-    {
-    case CHIP_JNI_ERROR_TYPE_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI type not found";
-        break;
-    case CHIP_JNI_ERROR_METHOD_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI method not found";
-        break;
-    case CHIP_JNI_ERROR_FIELD_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: JNI field not found";
-        break;
-    case CHIP_JNI_ERROR_DEVICE_NOT_FOUND.AsInteger():
-        errStr = "CHIP Device Controller Error: Device not found";
-        break;
-    default:
-        errStr = ErrorStr(inErr);
-        break;
-    }
-    errStrObj = (errStr != NULL) ? env->NewStringUTF(errStr) : NULL;
-
-    outEx =
-        (jthrowable) env->NewObject(sAndroidChipPlatformExceptionCls, constructor, static_cast<jint>(inErr.AsInteger()), errStrObj);
-    VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN);
-
-exit:
-    env->DeleteLocalRef(errStrObj);
-    return err;
-}
-
+#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
 static bool JavaBytesToUUID(JNIEnv * env, jbyteArray value, chip::Ble::ChipBleUUID & uuid)
 {
     const auto valueBegin  = env->GetByteArrayElements(value, nullptr);
@@ -280,3 +239,4 @@
     env->ReleaseByteArrayElements(value, valueBegin, 0);
     return result;
 }
+#endif
diff --git a/src/platform/android/java/chip/platform/NsdManagerServiceResolver.java b/src/platform/android/java/chip/platform/NsdManagerServiceResolver.java
index b086865..f5bb855 100644
--- a/src/platform/android/java/chip/platform/NsdManagerServiceResolver.java
+++ b/src/platform/android/java/chip/platform/NsdManagerServiceResolver.java
@@ -149,12 +149,12 @@
 
           @Override
           public void onServiceRegistered(NsdServiceInfo serviceInfo) {
-            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered:");
+            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered");
           }
 
           @Override
           public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
-            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered:");
+            Log.i(TAG, "service " + serviceInfo.getServiceName() + " onServiceRegistered");
           }
         };
     registrationListeners.add(registrationListener);
diff --git a/src/setup_payload/java/SetupPayloadParser-JNI.cpp b/src/setup_payload/java/SetupPayloadParser-JNI.cpp
index 5821aa1..0cc1010 100644
--- a/src/setup_payload/java/SetupPayloadParser-JNI.cpp
+++ b/src/setup_payload/java/SetupPayloadParser-JNI.cpp
@@ -1,8 +1,13 @@
+#include "lib/core/CHIPError.h"
+#include "lib/support/JniTypeWrappers.h"
+#include <setup_payload/ManualSetupPayloadGenerator.h>
 #include <setup_payload/ManualSetupPayloadParser.h>
+#include <setup_payload/QRCodeSetupPayloadGenerator.h>
 #include <setup_payload/QRCodeSetupPayloadParser.h>
 
 #include <lib/support/CHIPMem.h>
 #include <lib/support/CodeUtils.h>
+#include <lib/support/JniReferences.h>
 #include <lib/support/logging/CHIPLogging.h>
 
 #include <vector>
@@ -23,6 +28,8 @@
 
 static jobject TransformSetupPayload(JNIEnv * env, SetupPayload & payload);
 static jobject CreateCapabilitiesHashSet(JNIEnv * env, RendezvousInformationFlags flags);
+static void TransformSetupPayloadFromJobject(JNIEnv * env, jobject jPayload, SetupPayload & payload);
+static void CreateCapabilitiesFromHashSet(JNIEnv * env, jobject discoveryCapabilitiesObj, RendezvousInformationFlags & flags);
 static CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj);
 static CHIP_ERROR ThrowInvalidEntryCodeFormatException(JNIEnv * env, jstring entryCodeObj);
 
@@ -196,6 +203,100 @@
     return capabilitiesHashSet;
 }
 
+JNI_METHOD(jstring, getQrCodeFromPayload)(JNIEnv * env, jobject self, jobject setupPayload)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    SetupPayload payload;
+    std::string qrString;
+
+    TransformSetupPayloadFromJobject(env, setupPayload, payload);
+
+    err = QRCodeSetupPayloadGenerator(payload).payloadBase38Representation(qrString);
+    if (err != CHIP_NO_ERROR)
+    {
+        jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException");
+        JniReferences::GetInstance().ThrowError(env, exceptionCls, err);
+        return nullptr;
+    }
+
+    return env->NewStringUTF(qrString.c_str());
+}
+
+JNI_METHOD(jstring, getManualEntryCodeFromPayload)(JNIEnv * env, jobject self, jobject setupPayload)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    SetupPayload payload;
+    std::string outDecimalString;
+
+    TransformSetupPayloadFromJobject(env, setupPayload, payload);
+
+    err = ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(outDecimalString);
+    if (err != CHIP_NO_ERROR)
+    {
+        jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException");
+        JniReferences::GetInstance().ThrowError(env, exceptionCls, err);
+        return nullptr;
+    }
+
+    return env->NewStringUTF(outDecimalString.c_str());
+}
+
+void TransformSetupPayloadFromJobject(JNIEnv * env, jobject jPayload, SetupPayload & payload)
+{
+    jclass setupPayloadClass = env->FindClass("chip/setuppayload/SetupPayload");
+
+    jfieldID version               = env->GetFieldID(setupPayloadClass, "version", "I");
+    jfieldID vendorId              = env->GetFieldID(setupPayloadClass, "vendorId", "I");
+    jfieldID productId             = env->GetFieldID(setupPayloadClass, "productId", "I");
+    jfieldID commissioningFlow     = env->GetFieldID(setupPayloadClass, "commissioningFlow", "I");
+    jfieldID discriminator         = env->GetFieldID(setupPayloadClass, "discriminator", "I");
+    jfieldID setUpPinCode          = env->GetFieldID(setupPayloadClass, "setupPinCode", "J");
+    jfieldID discoveryCapabilities = env->GetFieldID(setupPayloadClass, "discoveryCapabilities", "Ljava/util/Set;");
+
+    payload.version           = env->GetIntField(jPayload, version);
+    payload.vendorID          = env->GetIntField(jPayload, vendorId);
+    payload.productID         = env->GetIntField(jPayload, productId);
+    payload.commissioningFlow = static_cast<CommissioningFlow>(env->GetIntField(jPayload, commissioningFlow));
+    payload.discriminator     = env->GetIntField(jPayload, discriminator);
+    payload.setUpPINCode      = env->GetLongField(jPayload, setUpPinCode);
+
+    jobject discoveryCapabilitiesObj = env->GetObjectField(jPayload, discoveryCapabilities);
+    CreateCapabilitiesFromHashSet(env, discoveryCapabilitiesObj, payload.rendezvousInformation);
+}
+
+void CreateCapabilitiesFromHashSet(JNIEnv * env, jobject discoveryCapabilitiesObj, RendezvousInformationFlags & flags)
+{
+    jclass hashSetClass             = env->FindClass("java/util/HashSet");
+    jmethodID hashSetContainsMethod = env->GetMethodID(hashSetClass, "contains", "(Ljava/lang/Object;)Z");
+
+    jboolean contains;
+    jclass capabilityEnum = env->FindClass("chip/setuppayload/DiscoveryCapability");
+
+    jfieldID bleCapability = env->GetStaticFieldID(capabilityEnum, "BLE", "Lchip/setuppayload/DiscoveryCapability;");
+    jobject bleObj         = env->GetStaticObjectField(capabilityEnum, bleCapability);
+    contains               = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, bleObj);
+    if (contains)
+    {
+        flags.Set(chip::RendezvousInformationFlag::kBLE);
+    }
+
+    jfieldID softApCapability = env->GetStaticFieldID(capabilityEnum, "SOFT_AP", "Lchip/setuppayload/DiscoveryCapability;");
+    jobject softApObj         = env->GetStaticObjectField(capabilityEnum, softApCapability);
+    contains                  = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, softApObj);
+    if (contains)
+    {
+        flags.Set(chip::RendezvousInformationFlag::kSoftAP);
+    }
+
+    jfieldID onNetworkCapability = env->GetStaticFieldID(capabilityEnum, "ON_NETWORK", "Lchip/setuppayload/DiscoveryCapability;");
+    jobject onNetworkObj         = env->GetStaticObjectField(capabilityEnum, onNetworkCapability);
+    contains                     = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, onNetworkObj);
+    if (contains)
+    {
+        flags.Set(chip::RendezvousInformationFlag::kOnNetwork);
+    }
+}
+
 CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj)
 {
     jclass exceptionCls            = nullptr;
diff --git a/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java b/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java
index 1277408..b1ba720 100644
--- a/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java
+++ b/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java
@@ -14,6 +14,13 @@
     return fetchPayloadFromManualEntryCode(entryCodeString);
   }
 
+  /** Get QR code string from {@link SetupPayload}. */
+  public native String getQrCodeFromPayload(SetupPayload payload) throws SetupPayloadException;
+
+  /** Get manual entry code string from {@link SetupPayload}. */
+  public native String getManualEntryCodeFromPayload(SetupPayload payload)
+      throws SetupPayloadException;
+
   private native SetupPayload fetchPayloadFromQrCode(String qrCodeString)
       throws UnrecognizedQrCodeException;
 
@@ -39,4 +46,14 @@
       super(String.format("Invalid format for entry code string: %s", entryCode), null);
     }
   }
+
+  public static class SetupPayloadException extends Exception {
+    private static final long serialVersionUID = 1L;
+    public int errorCode;
+
+    public SetupPayloadException(int errorCode, String message) {
+      super(message != null ? message : String.format("Error Code %d", errorCode));
+      this.errorCode = errorCode;
+    }
+  }
 }