diff --git a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
index fcb987edb574aa..9d0bf693ac59f3 100644
--- a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
+++ b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
@@ -5583,6 +5583,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/examples/placeholder/linux/apps/app1/config.matter b/examples/placeholder/linux/apps/app1/config.matter
index 87b61c8a45357c..027ced1a0a93c1 100644
--- a/examples/placeholder/linux/apps/app1/config.matter
+++ b/examples/placeholder/linux/apps/app1/config.matter
@@ -8236,6 +8236,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
@@ -8295,6 +8298,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/examples/placeholder/linux/apps/app2/config.matter b/examples/placeholder/linux/apps/app2/config.matter
index db46fa85d7e71d..2ba0b14e680f08 100644
--- a/examples/placeholder/linux/apps/app2/config.matter
+++ b/examples/placeholder/linux/apps/app2/config.matter
@@ -8193,6 +8193,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
@@ -8252,6 +8255,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/examples/tv-app/android/App/platform-app/build.gradle b/examples/tv-app/android/App/platform-app/build.gradle
index 0845105bffccc5..5b4203e9c1509c 100644
--- a/examples/tv-app/android/App/platform-app/build.gradle
+++ b/examples/tv-app/android/App/platform-app/build.gradle
@@ -72,4 +72,4 @@ dependencies {
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
     implementation 'com.google.zxing:core:3.3.0'
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-}
+}
\ No newline at end of file
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ApplicationLauncherManagerImpl.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ApplicationLauncherManagerImpl.java
new file mode 100644
index 00000000000000..bb1856ec6e14d1
--- /dev/null
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ApplicationLauncherManagerImpl.java
@@ -0,0 +1,165 @@
+package com.matter.tv.server.handlers;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import com.matter.tv.server.tvapp.Application;
+import com.matter.tv.server.tvapp.ApplicationLauncherManager;
+import com.matter.tv.server.tvapp.LauncherResponse;
+import com.matter.tv.server.utils.EndpointsDataStore;
+import com.matter.tv.server.utils.InstallationObserver;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class ApplicationLauncherManagerImpl implements ApplicationLauncherManager {
+
+  private static final String TAG = "ApplicationLauncherService";
+
+  private volatile boolean registered = false;
+  private PackageManager packageManager;
+  private EndpointsDataStore endpointsDataStore;
+
+  /** Hash Map of packageName & Install Status */
+  private Map<String, InstallationObserver.InstallStatus> lastReceivedInstallationStatus =
+      new HashMap<>();
+
+  private LiveData<InstallationObserver.InstallState> installStateLiveData;
+
+  public ApplicationLauncherManagerImpl(Context context) {
+    packageManager = context.getPackageManager();
+    endpointsDataStore = EndpointsDataStore.getInstance(context);
+    registerSelf(context);
+  }
+
+  private final Observer<InstallationObserver.InstallState> installStateObserver =
+      state -> {
+        lastReceivedInstallationStatus.put(state.getAppPackageName(), state.getStatus());
+        switch (state.getStatus()) {
+          case IN_PROGRESS:
+            // Installation is in progress
+            Log.d(TAG, "Installation of " + state.getAppPackageName() + " in progress");
+            break;
+          case SUCCEEDED:
+            // Installation succeeded
+            Log.d(TAG, "Installation of " + state.getAppPackageName() + " succeeded");
+            break;
+          case FAILED:
+            // Installation failed
+            Log.d(TAG, "Installation of " + state.getAppPackageName() + " failed");
+            break;
+        }
+      };
+
+  private void stopObservingInstallations() {
+    if (installStateLiveData != null) {
+      Log.d("InstallationObserver", "Stopped Observing");
+      installStateLiveData.removeObserver(installStateObserver);
+    }
+  }
+
+  public void unregister() {
+    stopObservingInstallations();
+  }
+
+  private void registerSelf(Context context) {
+    if (registered) {
+      Log.i(TAG, "Package update receiver for matter already registered");
+      return;
+    } else {
+      registered = true;
+    }
+    Log.i(TAG, "Registered the matter package updates receiver");
+
+    installStateLiveData = InstallationObserver.installationStates(context);
+    installStateLiveData.observeForever(installStateObserver);
+    Log.d(TAG, "Started Observing package installations");
+  }
+
+  @Override
+  public int[] getCatalogList() {
+    Log.i(TAG, "Get Catalog List");
+    return new int[] {123, 456, 89010};
+  }
+
+  @Override
+  public LauncherResponse launchApp(Application app, String data) {
+    Log.i(
+        TAG,
+        "Launch app id:" + app.applicationId + " cid:" + app.catalogVendorId + " data:" + data);
+
+    int status = 0;
+    String responseData = "";
+
+    // Installed Apps that have declared CSA product id & vendor id in their manifes
+    boolean matterEnabledAppdIsInstalled =
+        endpointsDataStore.getAllPersistedContentApps().containsKey(app.applicationId);
+    // Installed App
+    boolean appIsInstalled =
+        InstallationObserver.getInstalledPackages(packageManager).contains(app.applicationId);
+    boolean isAppInstalling =
+        Objects.equals(
+            lastReceivedInstallationStatus.get(app.applicationId),
+            InstallationObserver.InstallStatus.IN_PROGRESS);
+    boolean appInstallFailed =
+        Objects.equals(
+            lastReceivedInstallationStatus.get(app.applicationId),
+            InstallationObserver.InstallStatus.FAILED);
+
+    // This use-case can happen if app is installed
+    // but it does not support Matter
+    if (!matterEnabledAppdIsInstalled && appIsInstalled) {
+      Log.i(
+          TAG,
+          "Matter enabled app is not installed, but app is installed. Launching app's install page");
+      status = LauncherResponse.STATUS_PENDING_USER_APPROVAL;
+      responseData = "App is installed, try updating";
+
+      //
+      // Add code to launch App Install Page
+      //
+
+    } else if (!matterEnabledAppdIsInstalled && !appIsInstalled) {
+      Log.i(
+          TAG,
+          "Matter enabled app is not installed and app is not installed. Launching app's install page");
+      if (isAppInstalling) {
+        Log.i(TAG, "App is installing");
+        status = LauncherResponse.STATUS_INSTALLING;
+      } else {
+        status = LauncherResponse.STATUS_PENDING_USER_APPROVAL;
+        if (appInstallFailed) {
+          responseData = "App install failed. Try again";
+        }
+      }
+
+      //
+      // Add code to launch App Install Page
+      //
+
+    } else if (matterEnabledAppdIsInstalled && appIsInstalled) {
+      Log.i(TAG, "Launching the app");
+      status = LauncherResponse.STATUS_SUCCESS;
+
+      //
+      // Add code to launch an app
+      //
+    }
+
+    return new LauncherResponse(status, responseData);
+  }
+
+  @Override
+  public LauncherResponse stopApp(Application app) {
+    Log.i(TAG, "Stop app id:" + app.applicationId + " cid:" + app.catalogVendorId);
+    return new LauncherResponse(LauncherResponse.STATUS_SUCCESS, "");
+  }
+
+  @Override
+  public LauncherResponse hideApp(Application app) {
+    Log.i(TAG, "Hide app id:" + app.applicationId + " cid:" + app.catalogVendorId);
+    return new LauncherResponse(LauncherResponse.STATUS_SUCCESS, "");
+  }
+}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
index c1d479e6d147e1..683fb226c9e441 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServant.java
@@ -30,6 +30,8 @@
 import chip.platform.PreferencesConfigurationManager;
 import chip.platform.PreferencesKeyValueStoreManager;
 import com.matter.tv.server.MatterCommissioningPrompter;
+import com.matter.tv.server.handlers.ApplicationLauncherManagerImpl;
+import com.matter.tv.server.tvapp.ApplicationLauncherManager;
 import com.matter.tv.server.tvapp.ChannelManagerStub;
 import com.matter.tv.server.tvapp.Clusters;
 import com.matter.tv.server.tvapp.ContentLaunchManagerStub;
@@ -55,6 +57,8 @@ public class MatterServant {
   private boolean mIsOn = true;
   private int mOnOffEndpoint;
   private int mLevelEndpoint;
+  private MatterCommissioningPrompter matterCommissioningPrompter;
+  private ApplicationLauncherManager applicationLauncherManager;
 
   private MatterServant() {}
 
@@ -72,17 +76,25 @@ public void init(@NonNull Context context) {
 
     this.context = context;
 
+    this.applicationLauncherManager = new ApplicationLauncherManagerImpl(context);
+
     // The order is important, must
     // first new TvApp to load dynamic library
     // then chipPlatform to prepare platform
     // then TvApp.preServerInit to initialize any server configuration
     // then start ChipAppServer
     // then TvApp.postServerInit to init app platform
+    //
+    // TODO: Move all of the bellow KeypadInputManager...LevelManagerStub to
+    // PlatformAppCommandDelegate
+    // There is no need for this complicated logic
     mTvApp =
         new TvApp(
             (app, clusterId, endpoint) -> {
               if (clusterId == Clusters.ClusterId_KeypadInput) {
                 app.setKeypadInputManager(endpoint, new KeypadInputManagerStub(endpoint));
+              } else if (clusterId == Clusters.ClusterId_ApplicationLauncher) {
+                app.setApplicationLauncherManager(endpoint, applicationLauncherManager);
               } else if (clusterId == Clusters.ClusterId_WakeOnLan) {
                 app.setWakeOnLanManager(endpoint, new WakeOnLanManagerStub(endpoint));
               } else if (clusterId == Clusters.ClusterId_MediaInput) {
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java
index 227ea9326be538..7b3822bb1b59a4 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java
@@ -4,6 +4,7 @@
 import android.content.SharedPreferences;
 import android.util.JsonReader;
 import android.util.JsonWriter;
+import android.util.Log;
 import com.matter.tv.app.api.SupportedCluster;
 import com.matter.tv.server.model.ContentApp;
 import java.io.IOException;
@@ -59,6 +60,7 @@ public Map<String, ContentApp> getAllPersistedContentApps() {
   }
 
   public void persistContentAppEndpoint(ContentApp app) {
+    Log.i(EndpointsDataStore.class.toString(), "Persist Content App Endpoint " + app.getAppName());
     persistedContentApps.put(app.getAppName(), app);
     discoveredEndpoints.edit().putString(app.getAppName(), serializeContentApp(app)).apply();
   }
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/InstallationObserver.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/InstallationObserver.java
new file mode 100644
index 00000000000000..c47359272a1359
--- /dev/null
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/InstallationObserver.java
@@ -0,0 +1,137 @@
+package com.matter.tv.server.utils;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+public class InstallationObserver {
+  public enum InstallStatus {
+    IN_PROGRESS,
+    SUCCEEDED,
+    FAILED
+  }
+
+  public static class InstallState {
+    private final String appPackageName;
+    private final InstallStatus status;
+
+    public InstallState(String appPackageName, InstallStatus status) {
+      this.appPackageName = appPackageName;
+      this.status = status;
+    }
+
+    public String getAppPackageName() {
+      return appPackageName;
+    }
+
+    public InstallStatus getStatus() {
+      return status;
+    }
+
+    public String getPackageShortName() {
+      return appPackageName.substring(appPackageName.lastIndexOf(".") + 1);
+    }
+
+    public String getPackageTitle() {
+      String shortName = getPackageShortName();
+      return shortName.substring(0, 1).toUpperCase(Locale.getDefault()) + shortName.substring(1);
+    }
+  }
+
+  private static InstallState stateFromId(PackageInstaller installer, int sessionId) {
+    PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
+    if (info == null || info.getAppPackageName() == null) {
+      return null;
+    }
+    return new InstallState(info.getAppPackageName(), InstallStatus.IN_PROGRESS);
+  }
+
+  public static Set<String> getInstalledPackages(PackageManager packageManager) {
+    List<PackageInfo> packageInfoList = packageManager.getInstalledPackages(0);
+    Set<String> setOfInstalledApps = new HashSet<>();
+    for (PackageInfo info : packageInfoList) {
+      setOfInstalledApps.add(info.packageName);
+    }
+    return setOfInstalledApps;
+  }
+
+  public static LiveData<InstallState> installationStates(Context context) {
+    MutableLiveData<InstallState> liveData = new MutableLiveData<>();
+    PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+    Map<Integer, InstallState> inProgressSessions = new HashMap<>();
+
+    PackageInstaller.SessionCallback callback =
+        new PackageInstaller.SessionCallback() {
+          @Override
+          public void onCreated(int sessionId) {
+            Log.d(TAG, "onCreated ");
+            InstallState state = stateFromId(packageInstaller, sessionId);
+            if (state != null) {
+              inProgressSessions.put(sessionId, state);
+              liveData.postValue(state);
+            }
+          }
+
+          @Override
+          public void onBadgingChanged(int sessionId) {}
+
+          @Override
+          public void onActiveChanged(int sessionId, boolean active) {
+            Log.d(TAG, "onActiveChanged");
+            InstallState state = stateFromId(packageInstaller, sessionId);
+            if (state != null) {
+              inProgressSessions.put(sessionId, state);
+              liveData.postValue(state);
+            }
+          }
+
+          @Override
+          public void onProgressChanged(int sessionId, float progress) {
+            Log.d(TAG, "onProgressChanged:" + progress);
+            InstallState state = stateFromId(packageInstaller, sessionId);
+            if (state != null) {
+              inProgressSessions.put(sessionId, state);
+              liveData.postValue(state);
+            }
+          }
+
+          @Override
+          public void onFinished(int sessionId, boolean success) {
+            Log.d(TAG, "onFinished " + sessionId + " " + success);
+            InstallState current = inProgressSessions.get(sessionId);
+            if (current != null) {
+              InstallState newState =
+                  new InstallState(
+                      current.getAppPackageName(),
+                      success ? InstallStatus.SUCCEEDED : InstallStatus.FAILED);
+              liveData.postValue(newState);
+              inProgressSessions.remove(sessionId);
+            }
+          }
+        };
+
+    packageInstaller.registerSessionCallback(callback);
+
+    for (PackageInstaller.SessionInfo info : packageInstaller.getMySessions()) {
+      if (info.isActive() && info.getAppPackageName() != null) {
+        InstallState state = new InstallState(info.getAppPackageName(), InstallStatus.IN_PROGRESS);
+        inProgressSessions.put(info.getSessionId(), state);
+        liveData.postValue(state);
+      }
+    }
+
+    return liveData;
+  }
+
+  private static final String TAG = "InstallationObserver";
+}
diff --git a/examples/tv-app/android/BUILD.gn b/examples/tv-app/android/BUILD.gn
index 38c75d9273c243..f08f16f363e516 100644
--- a/examples/tv-app/android/BUILD.gn
+++ b/examples/tv-app/android/BUILD.gn
@@ -28,8 +28,6 @@ shared_library("jni") {
     "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-init.cpp",
@@ -80,6 +78,8 @@ shared_library("jni") {
     "java/TVApp-JNI.cpp",
     "java/WakeOnLanManager.cpp",
     "java/WakeOnLanManager.h",
+    "java/application-launcher/ApplicationLauncherManager.cpp",
+    "java/application-launcher/ApplicationLauncherManager.h",
   ]
 
   deps = [
@@ -115,6 +115,8 @@ android_library("java") {
   sources = [
     "java/src/com/matter/tv/server/tvapp/AppPlatform.java",
     "java/src/com/matter/tv/server/tvapp/AppPlatformShellCommands.java",
+    "java/src/com/matter/tv/server/tvapp/Application.java",
+    "java/src/com/matter/tv/server/tvapp/ApplicationLauncherManager.java",
     "java/src/com/matter/tv/server/tvapp/ChannelInfo.java",
     "java/src/com/matter/tv/server/tvapp/ChannelLineupInfo.java",
     "java/src/com/matter/tv/server/tvapp/ChannelManager.java",
@@ -136,6 +138,7 @@ android_library("java") {
     "java/src/com/matter/tv/server/tvapp/DeviceEventProvider.java",
     "java/src/com/matter/tv/server/tvapp/KeypadInputManager.java",
     "java/src/com/matter/tv/server/tvapp/KeypadInputManagerStub.java",
+    "java/src/com/matter/tv/server/tvapp/LauncherResponse.java",
     "java/src/com/matter/tv/server/tvapp/LevelManager.java",
     "java/src/com/matter/tv/server/tvapp/LevelManagerStub.java",
     "java/src/com/matter/tv/server/tvapp/LowPowerManager.java",
diff --git a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp b/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp
deleted file mode 100644
index c915856f48fd4d..00000000000000
--- a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- *
- *    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"
-
-using namespace std;
-using namespace chip::app;
-using namespace chip::app::Clusters;
-using namespace chip::app::Clusters::ApplicationLauncher;
-using namespace chip::Uint8;
-
-CHIP_ERROR ApplicationLauncherManager::HandleGetCatalogList(AttributeValueEncoder & aEncoder)
-{
-    std::list<uint16_t> catalogList = { 123, 456 };
-    return aEncoder.EncodeList([catalogList](const auto & encoder) -> CHIP_ERROR {
-        for (const auto & catalog : catalogList)
-        {
-            ReturnErrorOnFailure(encoder.Encode(catalog));
-        }
-        return CHIP_NO_ERROR;
-    });
-}
-
-void ApplicationLauncherManager::HandleLaunchApp(CommandResponseHelper<LauncherResponseType> & helper, const ByteSpan & data,
-                                                 const ApplicationType & application)
-{
-    ChipLogProgress(Zcl, "ApplicationLauncherManager::HandleLaunchApp");
-
-    // TODO: Insert code here
-    LauncherResponseType response;
-    const char * buf = "data";
-    response.data.SetValue(ByteSpan(from_const_char(buf), strlen(buf)));
-    response.status = StatusEnum::kSuccess;
-    helper.Success(response);
-}
-
-void ApplicationLauncherManager::HandleStopApp(CommandResponseHelper<LauncherResponseType> & helper,
-                                               const ApplicationType & application)
-{
-    ChipLogProgress(Zcl, "ApplicationLauncherManager::HandleStopApp");
-
-    // TODO: Insert code here
-    LauncherResponseType response;
-    const char * buf = "data";
-    response.data.SetValue(ByteSpan(from_const_char(buf), strlen(buf)));
-    response.status = StatusEnum::kSuccess;
-    helper.Success(response);
-}
-
-void ApplicationLauncherManager::HandleHideApp(CommandResponseHelper<LauncherResponseType> & helper,
-                                               const ApplicationType & application)
-{
-    ChipLogProgress(Zcl, "ApplicationLauncherManager::HandleHideApp");
-
-    // TODO: Insert code here
-    LauncherResponseType response;
-    const char * buf = "data";
-    response.data.SetValue(ByteSpan(from_const_char(buf), strlen(buf)));
-    response.status = StatusEnum::kSuccess;
-    helper.Success(response);
-}
diff --git a/examples/tv-app/android/include/cluster-init.cpp b/examples/tv-app/android/include/cluster-init.cpp
index e6f0cd8b481f57..7481625b664a21 100644
--- a/examples/tv-app/android/include/cluster-init.cpp
+++ b/examples/tv-app/android/include/cluster-init.cpp
@@ -17,7 +17,6 @@
  */
 
 #include "application-basic/ApplicationBasicManager.h"
-#include "application-launcher/ApplicationLauncherManager.h"
 #include "audio-output/AudioOutputManager.h"
 #include "target-navigator/TargetNavigatorManager.h"
 
@@ -30,7 +29,6 @@ using namespace chip;
 
 namespace {
 static ApplicationBasicManager applicationBasicManager;
-static ApplicationLauncherManager applicationLauncherManager;
 static AudioOutputManager audioOutputManager;
 static TargetNavigatorManager targetNavigatorManager;
 } // namespace
@@ -50,21 +48,6 @@ void emberAfApplicationBasicClusterInitCallback(chip::EndpointId endpoint)
     chip::app::Clusters::ApplicationBasic::SetDefaultDelegate(endpoint, &applicationBasicManager);
 }
 
-/** @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)
-{
-    ChipLogProgress(Zcl, "TV Android App: ApplicationLauncher::SetDefaultDelegate");
-    chip::app::Clusters::ApplicationLauncher::SetDefaultDelegate(endpoint, &applicationLauncherManager);
-}
-
 /** @brief Audio Output Cluster Init
  *
  * This function is called when a specific cluster is initialized. It gives the
diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp
index d04964ee5eb747..7ac1499ab64f55 100644
--- a/examples/tv-app/android/java/AppImpl.cpp
+++ b/examples/tv-app/android/java/AppImpl.cpp
@@ -503,6 +503,18 @@ EndpointId ContentAppFactoryImpl::RemoveContentApp(EndpointId epId)
     return kInvalidEndpointId;
 }
 
+void ContentAppFactoryImpl::LogInstalledApps()
+{
+    for (auto & contentApp : mContentApps)
+    {
+        ChipLogProgress(DeviceLayer, "Content app vid=%d pid=%d id=%s is on ep=%d",
+                        contentApp->GetApplicationBasicDelegate()->HandleGetVendorId(),
+                        contentApp->GetApplicationBasicDelegate()->HandleGetProductId(),
+                        contentApp->GetApplicationBasicDelegate()->GetCatalogVendorApp()->GetApplicationId(),
+                        contentApp->GetEndpointId());
+    }
+}
+
 void ContentAppFactoryImpl::AddAdminVendorId(uint16_t vendorId)
 {
     mAdminVendorIds.push_back(vendorId);
diff --git a/examples/tv-app/android/java/AppImpl.h b/examples/tv-app/android/java/AppImpl.h
index b10c96608c8fc1..a86a11c4386626 100644
--- a/examples/tv-app/android/java/AppImpl.h
+++ b/examples/tv-app/android/java/AppImpl.h
@@ -35,7 +35,6 @@
 
 #include "../include/account-login/AccountLoginManager.h"
 #include "../include/application-basic/ApplicationBasicManager.h"
-#include "../include/application-launcher/ApplicationLauncherManager.h"
 #include "../include/content-control/ContentController.h"
 #include "../include/content-launcher/AppContentLauncherManager.h"
 #include "../include/media-playback/AppMediaPlaybackManager.h"
@@ -45,6 +44,7 @@
 #include "ContentAppAttributeDelegate.h"
 #include "ContentAppCommandDelegate.h"
 #include "KeypadInputManager.h"
+#include "application-launcher/ApplicationLauncherManager.h"
 #include <app/clusters/account-login-server/account-login-delegate.h>
 #include <app/clusters/application-basic-server/application-basic-delegate.h>
 #include <app/clusters/application-launcher-server/application-launcher-delegate.h>
@@ -198,6 +198,8 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory
 
     void setContentAppCommandDelegate(ContentAppCommandDelegate * commandDelegate);
 
+    void LogInstalledApps();
+
 protected:
     // TODO: Update to use unique_ptr instead of raw pointers
     std::vector<ContentAppImpl *> mContentApps;
diff --git a/examples/tv-app/android/java/AppPlatformShellCommands-JNI.cpp b/examples/tv-app/android/java/AppPlatformShellCommands-JNI.cpp
index f15ef7bebae54c..cb491fcae91163 100644
--- a/examples/tv-app/android/java/AppPlatformShellCommands-JNI.cpp
+++ b/examples/tv-app/android/java/AppPlatformShellCommands-JNI.cpp
@@ -269,6 +269,17 @@ char * AppPlatformHandler(int argc, char ** argv)
         }
         return response;
     }
+    else if (strcmp(argv[0], "print-apps") == 0)
+    {
+        ContentAppFactoryImpl * factory = GetContentAppFactoryImpl();
+        factory->LogInstalledApps();
+
+        ChipLogProgress(DeviceLayer, "logged installed apps");
+
+        strcpy(response, "logged installed apps");
+
+        return response;
+    }
     else if (strcmp(argv[0], "remove-app-access") == 0)
     {
         Access::GetAccessControl().DeleteAllEntriesForFabric(GetDeviceCommissioner()->GetFabricIndex());
diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp
index 65a0a4409e9b8f..0fe1bdef939ca8 100644
--- a/examples/tv-app/android/java/TVApp-JNI.cpp
+++ b/examples/tv-app/android/java/TVApp-JNI.cpp
@@ -31,6 +31,7 @@
 #include "MyUserPrompter-JNI.h"
 #include "OnOffManager.h"
 #include "WakeOnLanManager.h"
+#include "application-launcher/ApplicationLauncherManager.h"
 #include "credentials/DeviceAttestationCredsProvider.h"
 #include <app/app-platform/ContentAppPlatform.h>
 #include <app/server/Dnssd.h>
@@ -139,6 +140,11 @@ JNI_METHOD(void, setMediaPlaybackManager)(JNIEnv *, jobject, jint endpoint, jobj
     MediaPlaybackManager::NewManager(endpoint, manager);
 }
 
+JNI_METHOD(void, setApplicationLauncherManager)(JNIEnv *, jobject, jint endpoint, jobject manager)
+{
+    ApplicationLauncherManager::NewManager(endpoint, manager);
+}
+
 JNI_METHOD(void, setMessagesManager)(JNIEnv *, jobject, jint endpoint, jobject manager)
 {
     MessagesManager::NewManager(endpoint, manager);
diff --git a/examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.cpp b/examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.cpp
new file mode 100644
index 00000000000000..8ee91292a5e76a
--- /dev/null
+++ b/examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.cpp
@@ -0,0 +1,345 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "ApplicationLauncherManager.h"
+#include "../TvApp-JNI.h"
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/util/config.h>
+#include <jni.h>
+#include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CHIPJNIError.h>
+#include <lib/support/JniReferences.h>
+#include <lib/support/JniTypeWrappers.h>
+
+using namespace std;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::ApplicationLauncher;
+using namespace chip::Uint8;
+
+void emberAfApplicationLauncherClusterInitCallback(chip::EndpointId endpoint)
+{
+    ChipLogProgress(Zcl, "TV Android App: ApplicationLauncher::PostClusterInit");
+    if (endpoint > kLocalVideoPlayerEndpointId)
+    {
+        ChipLogProgress(Zcl, "TV Android App: ignore setting the delegate for endpoints larger than 1");
+        return;
+    }
+    TvAppJNIMgr().PostClusterInit(chip::app::Clusters::ApplicationLauncher::Id, endpoint);
+}
+
+void ApplicationLauncherManager::NewManager(jint endpoint, jobject manager)
+{
+    if (endpoint > kLocalVideoPlayerEndpointId)
+    {
+        ChipLogProgress(Zcl, "TV Android App: ignore setting the delegate for endpoints larger than 1");
+        return;
+    }
+    ChipLogProgress(Zcl, "TV Android App: ApplicationLauncher::SetDefaultDelegate for endpoint: %d", endpoint);
+    ApplicationLauncherManager * mgr = new ApplicationLauncherManager();
+    mgr->InitializeWithObjects(manager);
+    chip::app::Clusters::ApplicationLauncher::SetDefaultDelegate(static_cast<chip::EndpointId>(endpoint), mgr);
+}
+
+CHIP_ERROR ApplicationLauncherManager::HandleGetCatalogList(AttributeValueEncoder & aEncoder)
+{
+    chip::DeviceLayer::StackUnlock unlock;
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    JNIEnv * env   = chip::JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+    chip::JniLocalReferenceScope scope(env);
+
+    ChipLogProgress(Zcl, "Received ApplicationLauncherManager::GetCatalogList");
+    VerifyOrExit(mApplicationLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mGetCatalogListMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+    env->ExceptionClear();
+
+    return aEncoder.EncodeList([this, env](const auto & encoder) -> CHIP_ERROR {
+        jintArray jCatalogList =
+            (jintArray) env->CallObjectMethod(mApplicationLauncherManagerObject.ObjectRef(), mGetCatalogListMethod);
+        if (env->ExceptionCheck())
+        {
+            ChipLogError(Zcl, "Java exception in ApplicationLauncherManager::GetCatalogList");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            return CHIP_ERROR_INCORRECT_STATE;
+        }
+
+        jint size       = env->GetArrayLength(jCatalogList);
+        jint * elements = env->GetIntArrayElements(jCatalogList, 0);
+        for (int i = 0; i < size; i++)
+        {
+            jint jCatalogVendorId = elements[i];
+            ReturnErrorOnFailure(encoder.Encode(static_cast<uint16_t>(jCatalogVendorId)));
+        }
+
+        return CHIP_NO_ERROR;
+    });
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "ApplicationLauncherManager::GetCatalogList status error: %s", err.AsString());
+    }
+
+    return err;
+}
+
+void ApplicationLauncherManager::HandleLaunchApp(CommandResponseHelper<LauncherResponseType> & helper, const ByteSpan & data,
+                                                 const ApplicationType & application)
+{
+    chip::DeviceLayer::StackUnlock unlock;
+    LauncherResponseType response;
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    JNIEnv * env   = chip::JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+    chip::JniLocalReferenceScope scope(env);
+
+    ChipLogProgress(Zcl, "Received ApplicationLauncherManager::LaunchApp");
+    VerifyOrExit(mApplicationLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mLaunchAppMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+    env->ExceptionClear();
+
+    {
+        // UtfString accepts const char * data
+        chip::UtfString jByteData(env, reinterpret_cast<const char *>(data.data()));
+
+        chip::UtfString jappId(env, application.applicationID);
+
+        // Create an instance of Application
+        jobject appObject = env->NewObject(mApplicationClass, mCreateApplicationMethod,
+                                           static_cast<jint>(application.catalogVendorID), jappId.jniValue());
+        VerifyOrReturn(appObject != nullptr, ChipLogError(Zcl, "Failed to create Application object"));
+
+        jobject resp =
+            env->CallObjectMethod(mApplicationLauncherManagerObject.ObjectRef(), mLaunchAppMethod, appObject, jByteData.jniValue());
+        if (env->ExceptionCheck())
+        {
+            ChipLogError(Zcl, "Java exception in ApplicationLauncherManager::LaunchApp");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            err = CHIP_ERROR_INCORRECT_STATE;
+            goto exit;
+        }
+
+        VerifyOrExit(resp != nullptr, err = CHIP_JNI_ERROR_NULL_OBJECT);
+        jclass respCls     = env->GetObjectClass(resp);
+        jfieldID statusFid = env->GetFieldID(respCls, "status", "I");
+        VerifyOrExit(statusFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jint status = env->GetIntField(resp, statusFid);
+
+        jfieldID dataFid = env->GetFieldID(respCls, "data", "Ljava/lang/String;");
+        VerifyOrExit(dataFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jstring jdataStr = (jstring) env->GetObjectField(resp, dataFid);
+        chip::JniUtfString dataStr(env, jdataStr);
+
+        response.status = static_cast<chip::app::Clusters::ApplicationLauncher::StatusEnum>(status);
+        response.data   = chip::Optional<chip::ByteSpan>(dataStr.byteSpan());
+
+        err = helper.Success(response);
+    }
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "ApplicationLauncherManager::LaunchApp status error: %s", err.AsString());
+    }
+}
+
+void ApplicationLauncherManager::HandleStopApp(CommandResponseHelper<LauncherResponseType> & helper,
+                                               const ApplicationType & application)
+{
+    chip::DeviceLayer::StackUnlock unlock;
+    LauncherResponseType response;
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    JNIEnv * env   = chip::JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+    chip::JniLocalReferenceScope scope(env);
+
+    ChipLogProgress(Zcl, "Received ApplicationLauncherManager::StopApp");
+    VerifyOrExit(mApplicationLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mStopAppMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+    env->ExceptionClear();
+
+    {
+        chip::UtfString jappId(env, application.applicationID);
+
+        // Create an instance of Application
+        jobject appObject = env->NewObject(mApplicationClass, mCreateApplicationMethod,
+                                           static_cast<jint>(application.catalogVendorID), jappId.jniValue());
+        VerifyOrReturn(appObject != nullptr, ChipLogError(Zcl, "Failed to create Application object"));
+
+        jobject resp = env->CallObjectMethod(mApplicationLauncherManagerObject.ObjectRef(), mStopAppMethod, appObject);
+        if (env->ExceptionCheck())
+        {
+            ChipLogError(Zcl, "Java exception in ApplicationLauncherManager::StopApp");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            err = CHIP_ERROR_INCORRECT_STATE;
+            goto exit;
+        }
+
+        VerifyOrExit(resp != nullptr, err = CHIP_JNI_ERROR_NULL_OBJECT);
+        jclass respCls     = env->GetObjectClass(resp);
+        jfieldID statusFid = env->GetFieldID(respCls, "status", "I");
+        VerifyOrExit(statusFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jint status = env->GetIntField(resp, statusFid);
+
+        jfieldID dataFid = env->GetFieldID(respCls, "data", "Ljava/lang/String;");
+        VerifyOrExit(dataFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jstring jdataStr = (jstring) env->GetObjectField(resp, dataFid);
+        chip::JniUtfString dataStr(env, jdataStr);
+
+        response.status = static_cast<chip::app::Clusters::ApplicationLauncher::StatusEnum>(status);
+        response.data   = chip::Optional<chip::ByteSpan>(dataStr.byteSpan());
+
+        err = helper.Success(response);
+    }
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "ApplicationLauncherManager::StopApp status error: %s", err.AsString());
+    }
+}
+
+void ApplicationLauncherManager::HandleHideApp(CommandResponseHelper<LauncherResponseType> & helper,
+                                               const ApplicationType & application)
+{
+    chip::DeviceLayer::StackUnlock unlock;
+    LauncherResponseType response;
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    JNIEnv * env   = chip::JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Could not get JNIEnv for current thread"));
+    chip::JniLocalReferenceScope scope(env);
+
+    ChipLogProgress(Zcl, "Received ApplicationLauncherManager::HideApp");
+    VerifyOrExit(mApplicationLauncherManagerObject.HasValidObjectRef(), err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(mHideAppMethod != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+    env->ExceptionClear();
+
+    {
+        chip::UtfString jappId(env, application.applicationID);
+
+        // Create an instance of Application
+        jobject appObject = env->NewObject(mApplicationClass, mCreateApplicationMethod,
+                                           static_cast<jint>(application.catalogVendorID), jappId.jniValue());
+        VerifyOrReturn(appObject != nullptr, ChipLogError(Zcl, "Failed to create Application object"));
+
+        jobject resp = env->CallObjectMethod(mApplicationLauncherManagerObject.ObjectRef(), mHideAppMethod, appObject);
+        if (env->ExceptionCheck())
+        {
+            ChipLogError(Zcl, "Java exception in ApplicationLauncherManager::HideApp");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            err = CHIP_ERROR_INCORRECT_STATE;
+            goto exit;
+        }
+
+        VerifyOrExit(resp != nullptr, err = CHIP_JNI_ERROR_NULL_OBJECT);
+        jclass respCls     = env->GetObjectClass(resp);
+        jfieldID statusFid = env->GetFieldID(respCls, "status", "I");
+        VerifyOrExit(statusFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jint status = env->GetIntField(resp, statusFid);
+
+        jfieldID dataFid = env->GetFieldID(respCls, "data", "Ljava/lang/String;");
+        VerifyOrExit(dataFid != nullptr, err = CHIP_JNI_ERROR_FIELD_NOT_FOUND);
+        jstring jdataStr = (jstring) env->GetObjectField(resp, dataFid);
+        chip::JniUtfString dataStr(env, jdataStr);
+
+        response.status = static_cast<chip::app::Clusters::ApplicationLauncher::StatusEnum>(status);
+        response.data   = chip::Optional<chip::ByteSpan>(dataStr.byteSpan());
+
+        err = helper.Success(response);
+    }
+
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(Zcl, "ApplicationLauncherManager::HideApp status error: %s", err.AsString());
+    }
+}
+
+void ApplicationLauncherManager::InitializeWithObjects(jobject managerObject)
+{
+    JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread();
+    VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Failed to GetEnvForCurrentThread for ApplicationLauncherManager"));
+
+    VerifyOrReturn(mApplicationLauncherManagerObject.Init(managerObject) == CHIP_NO_ERROR,
+                   ChipLogError(Zcl, "Failed to init mApplicationLauncherManagerObject"));
+
+    jclass applicationLauncherClass = env->GetObjectClass(managerObject);
+    VerifyOrReturn(applicationLauncherClass != nullptr, ChipLogError(Zcl, "Failed to get ApplicationLauncherManager Java class"));
+
+    mGetCatalogListMethod = env->GetMethodID(applicationLauncherClass, "getCatalogList", "()[I");
+    if (mGetCatalogListMethod == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to access ApplicationLauncherManager 'getCatalogList' method");
+        env->ExceptionClear();
+    }
+
+    mLaunchAppMethod = env->GetMethodID(applicationLauncherClass, "launchApp",
+                                        "(Lcom/matter/tv/server/tvapp/"
+                                        "Application;Ljava/lang/String;)Lcom/matter/tv/server/tvapp/LauncherResponse;");
+    if (mLaunchAppMethod == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to access ApplicationLauncherManager 'launchApp' method");
+        env->ExceptionClear();
+    }
+
+    mStopAppMethod = env->GetMethodID(applicationLauncherClass, "stopApp",
+                                      "(Lcom/matter/tv/server/tvapp/"
+                                      "Application;)Lcom/matter/tv/server/tvapp/LauncherResponse;");
+    if (mStopAppMethod == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to access ApplicationLauncherManager 'stopApp' method");
+        env->ExceptionClear();
+    }
+
+    mHideAppMethod = env->GetMethodID(applicationLauncherClass, "hideApp",
+                                      "(Lcom/matter/tv/server/tvapp/"
+                                      "Application;)Lcom/matter/tv/server/tvapp/LauncherResponse;");
+    if (mHideAppMethod == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to access ApplicationLauncherManager 'hideApp' method");
+        env->ExceptionClear();
+    }
+
+    // Find the Application class
+    jclass jc = env->FindClass("com/matter/tv/server/tvapp/Application");
+    // convert it to a global reference, otherwise code will crash
+    mApplicationClass = static_cast<jclass>(env->NewGlobalRef(jc));
+    if (mApplicationClass == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to find Application Java class");
+        env->ExceptionClear();
+    }
+
+    // Get the constructor method ID
+    mCreateApplicationMethod = env->GetMethodID(mApplicationClass, "<init>", "(ILjava/lang/String;)V");
+    if (mCreateApplicationMethod == nullptr)
+    {
+        ChipLogError(Zcl, "Failed to find constructor of Application Java class");
+        env->ExceptionClear();
+    }
+}
diff --git a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h b/examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.h
similarity index 74%
rename from examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h
rename to examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.h
index ffdd89ca8731e9..fd0f40d84b5b60 100644
--- a/examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.h
+++ b/examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.h
@@ -19,7 +19,9 @@
 #pragma once
 
 #include <app/clusters/application-launcher-server/application-launcher-server.h>
-#include <list>
+
+#include <jni.h>
+#include <lib/support/JniReferences.h>
 
 using chip::ByteSpan;
 using chip::app::AttributeValueEncoder;
@@ -28,11 +30,13 @@ using ApplicationLauncherDelegate = chip::app::Clusters::ApplicationLauncher::De
 using ApplicationType             = chip::app::Clusters::ApplicationLauncher::Structs::ApplicationStruct::Type;
 using LauncherResponseType        = chip::app::Clusters::ApplicationLauncher::Commands::LauncherResponse::Type;
 
+inline constexpr chip::EndpointId kLocalVideoPlayerEndpointId = 1;
+
 class ApplicationLauncherManager : public ApplicationLauncherDelegate
 {
 public:
-    ApplicationLauncherManager() : ApplicationLauncherDelegate(){};
-    ApplicationLauncherManager(bool featureMapContentPlatform) : ApplicationLauncherDelegate(featureMapContentPlatform){};
+    static void NewManager(jint endpoint, jobject manager);
+    void InitializeWithObjects(jobject managerObject);
 
     CHIP_ERROR HandleGetCatalogList(AttributeValueEncoder & aEncoder) override;
 
@@ -40,4 +44,12 @@ class ApplicationLauncherManager : public ApplicationLauncherDelegate
                          const ApplicationType & application) override;
     void HandleStopApp(CommandResponseHelper<LauncherResponseType> & helper, const ApplicationType & application) override;
     void HandleHideApp(CommandResponseHelper<LauncherResponseType> & helper, const ApplicationType & application) override;
+
+    chip::JniGlobalReference mApplicationLauncherManagerObject;
+    jmethodID mGetCatalogListMethod    = nullptr;
+    jmethodID mLaunchAppMethod         = nullptr;
+    jmethodID mStopAppMethod           = nullptr;
+    jmethodID mHideAppMethod           = nullptr;
+    jmethodID mCreateApplicationMethod = nullptr;
+    jclass mApplicationClass           = nullptr;
 };
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Application.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Application.java
new file mode 100644
index 00000000000000..bac89b09420213
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Application.java
@@ -0,0 +1,38 @@
+/*
+ *   Copyright (c) 2024 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+package com.matter.tv.server.tvapp;
+
+public class Application {
+
+  public int catalogVendorId;
+  public String applicationId;
+
+  public Application(int catalogVendorId, String applicationId) {
+    this.catalogVendorId = catalogVendorId;
+    this.applicationId = applicationId;
+  }
+
+  public Application createApplication(int catalogVendorId, String applicationId) {
+    return new Application(catalogVendorId, applicationId);
+  }
+
+  @Override
+  public String toString() {
+    return "Application{" + "catalogVendorId=" + catalogVendorId + ", applicationId='" + '\'' + '}';
+  }
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/ApplicationLauncherManager.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/ApplicationLauncherManager.java
new file mode 100644
index 00000000000000..7f80eb7a6c3421
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/ApplicationLauncherManager.java
@@ -0,0 +1,36 @@
+package com.matter.tv.server.tvapp;
+
+public interface ApplicationLauncherManager {
+
+  /**
+   * Return a list of available catalogs
+   *
+   * @return list of int
+   */
+  int[] getCatalogList();
+
+  /**
+   * Launch an app
+   *
+   * @param app that you want to launch
+   * @param data to send addditional data if needed
+   * @return launcher response with status
+   */
+  LauncherResponse launchApp(Application app, String data);
+
+  /**
+   * Stop an app
+   *
+   * @param app that you want to stop
+   * @return launcher response with status
+   */
+  LauncherResponse stopApp(Application app);
+
+  /**
+   * hide an app
+   *
+   * @param app that you want to hide
+   * @return launcher response with status
+   */
+  LauncherResponse hideApp(Application app);
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/LauncherResponse.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/LauncherResponse.java
new file mode 100644
index 00000000000000..c0a41d2b153969
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/LauncherResponse.java
@@ -0,0 +1,22 @@
+package com.matter.tv.server.tvapp;
+
+public class LauncherResponse {
+
+  public static final int STATUS_SUCCESS = 0;
+  public static final int STATUS_APP_NOT_AVAILABLE = 1;
+  public static final int STATUS_SYSTEM_BUSY = 2;
+  public static final int STATUS_PENDING_USER_APPROVAL = 3;
+  public static final int STATUS_DOWNLOADING = 4;
+  public static final int STATUS_INSTALLING = 5;
+
+  public LauncherResponse(int status, String data) {
+    this.status = status;
+    this.data = data;
+  }
+
+  /** The status in STATUS_XXX */
+  public int status;
+
+  /** Optional app-specific data. */
+  public String data;
+}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
index eaf207e45f3618..8cac42ff582e3e 100644
--- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/TvApp.java
@@ -47,6 +47,9 @@ private void postClusterInit(long clusterId, int endpoint) {
 
   public native void setKeypadInputManager(int endpoint, KeypadInputManager manager);
 
+  public native void setApplicationLauncherManager(
+      int endpoint, ApplicationLauncherManager manager);
+
   public native void setWakeOnLanManager(int endpoint, WakeOnLanManager manager);
 
   public native void setMediaInputManager(int endpoint, MediaInputManager manager);
diff --git a/examples/tv-app/tv-common/tv-app.matter b/examples/tv-app/tv-common/tv-app.matter
index 34aa4475c79677..f2a2a6f0affb0f 100644
--- a/examples/tv-app/tv-common/tv-app.matter
+++ b/examples/tv-app/tv-common/tv-app.matter
@@ -3259,6 +3259,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter b/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter
index 02aadc0439b50a..196e59a9edc720 100644
--- a/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter
+++ b/examples/tv-casting-app/tv-casting-common/tv-casting-app.matter
@@ -2699,6 +2699,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/src/app/clusters/application-basic-server/application-basic-delegate.h b/src/app/clusters/application-basic-server/application-basic-delegate.h
index 6d9b796ca29c39..deb148233a64ff 100644
--- a/src/app/clusters/application-basic-server/application-basic-delegate.h
+++ b/src/app/clusters/application-basic-server/application-basic-delegate.h
@@ -58,6 +58,8 @@ class DLL_EXPORT CatalogVendorApp
         Platform::CopyString(applicationId, sizeof(applicationId), appId);
     }
 
+    const char * GetApplicationId() { return applicationId; }
+
     static const int kApplicationIdSize = 32;
     char applicationId[kApplicationIdSize];
     uint16_t catalogVendorId;
diff --git a/src/app/clusters/application-launcher-server/application-launcher-server.cpp b/src/app/clusters/application-launcher-server/application-launcher-server.cpp
index ed6265e47c485b..c3e490d2c9119c 100644
--- a/src/app/clusters/application-launcher-server/application-launcher-server.cpp
+++ b/src/app/clusters/application-launcher-server/application-launcher-server.cpp
@@ -255,7 +255,7 @@ bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * co
         //  4. Call launch app command on Content App
         if (delegate->HasFeature(Feature::kApplicationPlatform))
         {
-            ChipLogError(Zcl, "ApplicationLauncher has content platform feature");
+            ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature");
             ContentApp * app = ContentAppPlatform::GetInstance().LoadContentApp(&vendorApp);
             if (app == nullptr)
             {
@@ -268,7 +268,7 @@ bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * co
 
             ContentAppPlatform::GetInstance().SetCurrentApp(app);
 
-            ChipLogError(Zcl, "ApplicationLauncher handling launch on ContentApp");
+            ChipLogProgress(Zcl, "ApplicationLauncher handling launch on ContentApp");
             app->GetApplicationLauncherDelegate()->HandleLaunchApp(responder, data.HasValue() ? data.Value() : ByteSpan(),
                                                                    application.Value());
             return true;
@@ -278,11 +278,11 @@ bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * co
        //  1. Set Content App status (basic cluster) to ACTIVE_VISIBLE_FOCUS
        //  2. Call launch app command on the given endpoint
 
-        ChipLogError(Zcl, "ApplicationLauncher no content platform feature");
+        ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature");
         ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint);
         if (appBasic != nullptr)
         {
-            ChipLogError(Zcl, "ApplicationLauncher setting basic cluster status to visible");
+            ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to visible");
             appBasic->SetApplicationStatus(ApplicationStatusEnum::kActiveVisibleFocus);
         }
 
@@ -292,9 +292,9 @@ bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * co
         {
             ContentAppPlatform::GetInstance().SetCurrentApp(app);
         }
-        else
+        else if (delegate->HasFeature(Feature::kApplicationPlatform))
         {
-            ChipLogError(Zcl, "ApplicationLauncher target app not found");
+            ChipLogProgress(Zcl, "ApplicationLauncher target app not found");
             LauncherResponseType response;
             response.status = StatusEnum::kAppNotAvailable;
             responder.Success(response);
@@ -302,7 +302,7 @@ bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * co
         }
 #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
 
-        ChipLogError(Zcl, "ApplicationLauncher handling launch");
+        ChipLogProgress(Zcl, "ApplicationLauncher handling launch");
         delegate->HandleLaunchApp(responder, data.HasValue() ? data.Value() : ByteSpan(), application.Value());
     }
 
@@ -349,7 +349,7 @@ bool emberAfApplicationLauncherClusterStopAppCallback(app::CommandHandler * comm
         //  4. Call stop app command on Content App
         if (delegate->HasFeature(Feature::kApplicationPlatform))
         {
-            ChipLogError(Zcl, "ApplicationLauncher has content platform feature");
+            ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature");
             ContentApp * app = ContentAppPlatform::GetInstance().LoadContentApp(&vendorApp);
             if (app == nullptr)
             {
@@ -362,10 +362,10 @@ bool emberAfApplicationLauncherClusterStopAppCallback(app::CommandHandler * comm
 
             ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app);
 
-            ChipLogError(Zcl, "ApplicationLauncher setting app status");
+            ChipLogProgress(Zcl, "ApplicationLauncher setting app status");
             app->GetApplicationBasicDelegate()->SetApplicationStatus(ApplicationStatusEnum::kStopped);
 
-            ChipLogError(Zcl, "ApplicationLauncher handling stop on ContentApp");
+            ChipLogProgress(Zcl, "ApplicationLauncher handling stop on ContentApp");
             app->GetApplicationLauncherDelegate()->HandleStopApp(responder, application.Value());
             return true;
         }
@@ -374,7 +374,7 @@ bool emberAfApplicationLauncherClusterStopAppCallback(app::CommandHandler * comm
        //  1. Set Content App status (basic cluster) to ACTIVE_STOPPED
        //  2. Call launch app command on the given endpoint
 
-        ChipLogError(Zcl, "ApplicationLauncher no content platform feature");
+        ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature");
 
 #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
         ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp);
@@ -388,7 +388,7 @@ bool emberAfApplicationLauncherClusterStopAppCallback(app::CommandHandler * comm
         ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint);
         if (appBasic != nullptr)
         {
-            ChipLogError(Zcl, "ApplicationLauncher setting basic cluster status to stopped");
+            ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to stopped");
             appBasic->SetApplicationStatus(ApplicationStatusEnum::kStopped);
         }
 
@@ -438,7 +438,7 @@ bool emberAfApplicationLauncherClusterHideAppCallback(app::CommandHandler * comm
         //  4. Call stop app command on Content App
         if (delegate->HasFeature(Feature::kApplicationPlatform))
         {
-            ChipLogError(Zcl, "ApplicationLauncher has content platform feature");
+            ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature");
             ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp);
             if (app == nullptr)
             {
@@ -451,7 +451,7 @@ bool emberAfApplicationLauncherClusterHideAppCallback(app::CommandHandler * comm
 
             ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app);
 
-            ChipLogError(Zcl, "ApplicationLauncher handling stop on ContentApp");
+            ChipLogProgress(Zcl, "ApplicationLauncher handling stop on ContentApp");
             app->GetApplicationLauncherDelegate()->HandleHideApp(responder, application.Value());
             return true;
         }
@@ -460,7 +460,7 @@ bool emberAfApplicationLauncherClusterHideAppCallback(app::CommandHandler * comm
        //  1. Set Content App status (basic cluster) to ACTIVE_VISIBLE_NOT_FOCUS
        //  2. Call launch app command on the given endpoint
 
-        ChipLogError(Zcl, "ApplicationLauncher no content platform feature");
+        ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature");
 
 #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
         ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp);
@@ -473,7 +473,7 @@ bool emberAfApplicationLauncherClusterHideAppCallback(app::CommandHandler * comm
         ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint);
         if (appBasic != nullptr)
         {
-            ChipLogError(Zcl, "ApplicationLauncher setting basic cluster status to stopped");
+            ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to stopped");
             appBasic->SetApplicationStatus(ApplicationStatusEnum::kActiveHidden);
         }
 
diff --git a/src/app/zap-templates/zcl/data-model/chip/application-launcher-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/application-launcher-cluster.xml
index 8aaca6201074dd..50b715e138a744 100644
--- a/src/app/zap-templates/zcl/data-model/chip/application-launcher-cluster.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/application-launcher-cluster.xml
@@ -80,5 +80,8 @@ limitations under the License.
     <item name="Success" value="0x00"/>
     <item name="AppNotAvailable" value="0x01"/>
     <item name="SystemBusy" value="0x02"/>
+    <item name="PendingUserApproval" value="0x03"/>
+    <item name="Downloading" value="0x04"/>
+    <item name="Installing" value="0x05"/>
   </enum>
 </configurator>
diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter
index 810f82819c1a19..770d6524ef021d 100644
--- a/src/controller/data_model/controller-clusters.matter
+++ b/src/controller/data_model/controller-clusters.matter
@@ -9114,6 +9114,9 @@ cluster ApplicationLauncher = 1292 {
     kSuccess = 0;
     kAppNotAvailable = 1;
     kSystemBusy = 2;
+    kPendingUserApproval = 3;
+    kDownloading = 4;
+    kInstalling = 5;
   }
 
   bitmap Feature : bitmap32 {
diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py
index 670c7109a5ccb8..b81eca3ed8aa50 100644
--- a/src/controller/python/chip/clusters/Objects.py
+++ b/src/controller/python/chip/clusters/Objects.py
@@ -45574,11 +45574,14 @@ class StatusEnum(MatterIntEnum):
             kSuccess = 0x00
             kAppNotAvailable = 0x01
             kSystemBusy = 0x02
+            kPendingUserApproval = 0x03
+            kDownloading = 0x04
+            kInstalling = 0x05
             # All received enum values that are not listed above will be mapped
             # to kUnknownEnumValue. This is a helper enum value that should only
             # be used by code to process how it handles receiving an unknown
             # enum value. This specific value should never be transmitted.
-            kUnknownEnumValue = 3,
+            kUnknownEnumValue = 6,
 
     class Bitmaps:
         class Feature(IntFlag):
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
index b8dfaa140e03e3..59c08128f910d0 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
@@ -21061,6 +21061,9 @@ typedef NS_ENUM(uint8_t, MTRApplicationLauncherStatus) {
     MTRApplicationLauncherStatusSuccess MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x00,
     MTRApplicationLauncherStatusAppNotAvailable MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x01,
     MTRApplicationLauncherStatusSystemBusy MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x02,
+    MTRApplicationLauncherStatusPendingUserApproval MTR_PROVISIONALLY_AVAILABLE = 0x03,
+    MTRApplicationLauncherStatusDownloading MTR_PROVISIONALLY_AVAILABLE = 0x04,
+    MTRApplicationLauncherStatusInstalling MTR_PROVISIONALLY_AVAILABLE = 0x05,
 } MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 
 typedef NS_OPTIONS(uint32_t, MTRApplicationLauncherFeature) {
diff --git a/src/lib/support/JniTypeWrappers.h b/src/lib/support/JniTypeWrappers.h
index 0e64666b3e328e..883b9228482b39 100644
--- a/src/lib/support/JniTypeWrappers.h
+++ b/src/lib/support/JniTypeWrappers.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <jni.h>
+#include <lib/core/CHIPSafeCasts.h>
 #include <lib/support/CHIPJNIError.h>
 #include <lib/support/JniReferences.h>
 #include <lib/support/Span.h>
@@ -50,6 +51,8 @@ class JniUtfString
 
     chip::CharSpan charSpan() const { return chip::CharSpan(c_str(), static_cast<size_t>(size())); }
 
+    chip::ByteSpan byteSpan() const { return chip::ByteSpan(chip::Uint8::from_const_char(c_str()), static_cast<size_t>(size())); }
+
     jsize size() const { return mDataLength; }
 
 private:
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
index 8a951e2a54a575..be23e2d1925c0c 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
@@ -3425,6 +3425,9 @@ static auto __attribute__((unused)) EnsureKnownEnumValue(ApplicationLauncher::St
     case EnumType::kSuccess:
     case EnumType::kAppNotAvailable:
     case EnumType::kSystemBusy:
+    case EnumType::kPendingUserApproval:
+    case EnumType::kDownloading:
+    case EnumType::kInstalling:
         return val;
     default:
         return EnumType::kUnknownEnumValue;
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
index a0407517696744..43bb08d1673567 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
@@ -5218,14 +5218,17 @@ namespace ApplicationLauncher {
 // Enum for StatusEnum
 enum class StatusEnum : uint8_t
 {
-    kSuccess         = 0x00,
-    kAppNotAvailable = 0x01,
-    kSystemBusy      = 0x02,
+    kSuccess             = 0x00,
+    kAppNotAvailable     = 0x01,
+    kSystemBusy          = 0x02,
+    kPendingUserApproval = 0x03,
+    kDownloading         = 0x04,
+    kInstalling          = 0x05,
     // All received enum values that are not listed above will be mapped
     // to kUnknownEnumValue. This is a helper enum value that should only
     // be used by code to process how it handles receiving and unknown
     // enum value. This specific should never be transmitted.
-    kUnknownEnumValue = 3,
+    kUnknownEnumValue = 6,
 };
 
 // Bitmap for Feature