From 8f1fe4faaef0a2491dd808142757f169ca69a7e9 Mon Sep 17 00:00:00 2001 From: Lazar Kovacic Date: Thu, 8 Aug 2024 02:27:21 +0200 Subject: [PATCH] Re-Implement App Install flow (#34141) * Add install things * Update the code * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * Restyled by gn * Revert the unecessary changes * Remove uncessary files * Update comment * Restyled by google-java-format * Fix get catalog list * Added option to log the content apps * Restyled by google-java-format * Restyled by clang-format --------- Co-authored-by: Restyled.io --- .../all-clusters-minimal-app.matter | 3 + .../placeholder/linux/apps/app1/config.matter | 6 + .../placeholder/linux/apps/app2/config.matter | 6 + .../android/App/platform-app/build.gradle | 2 +- .../ApplicationLauncherManagerImpl.java | 165 +++++++++ .../tv/server/service/MatterServant.java | 12 + .../tv/server/utils/EndpointsDataStore.java | 2 + .../tv/server/utils/InstallationObserver.java | 137 +++++++ examples/tv-app/android/BUILD.gn | 7 +- .../ApplicationLauncherManager.cpp | 76 ---- .../tv-app/android/include/cluster-init.cpp | 17 - examples/tv-app/android/java/AppImpl.cpp | 12 + examples/tv-app/android/java/AppImpl.h | 4 +- .../java/AppPlatformShellCommands-JNI.cpp | 11 + examples/tv-app/android/java/TVApp-JNI.cpp | 6 + .../ApplicationLauncherManager.cpp | 345 ++++++++++++++++++ .../ApplicationLauncherManager.h | 18 +- .../matter/tv/server/tvapp/Application.java | 38 ++ .../tvapp/ApplicationLauncherManager.java | 36 ++ .../tv/server/tvapp/LauncherResponse.java | 22 ++ .../src/com/matter/tv/server/tvapp/TvApp.java | 3 + examples/tv-app/tv-common/tv-app.matter | 3 + .../tv-casting-common/tv-casting-app.matter | 3 + .../application-basic-delegate.h | 2 + .../application-launcher-server.cpp | 32 +- .../chip/application-launcher-cluster.xml | 3 + .../data_model/controller-clusters.matter | 3 + .../python/chip/clusters/Objects.py | 5 +- .../CHIP/zap-generated/MTRBaseClusters.h | 3 + src/lib/support/JniTypeWrappers.h | 3 + .../zap-generated/cluster-enums-check.h | 3 + .../app-common/zap-generated/cluster-enums.h | 11 +- 32 files changed, 878 insertions(+), 121 deletions(-) create mode 100644 examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ApplicationLauncherManagerImpl.java create mode 100644 examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/InstallationObserver.java delete mode 100644 examples/tv-app/android/include/application-launcher/ApplicationLauncherManager.cpp create mode 100644 examples/tv-app/android/java/application-launcher/ApplicationLauncherManager.cpp rename examples/tv-app/android/{include => java}/application-launcher/ApplicationLauncherManager.h (74%) create mode 100644 examples/tv-app/android/java/src/com/matter/tv/server/tvapp/Application.java create mode 100644 examples/tv-app/android/java/src/com/matter/tv/server/tvapp/ApplicationLauncherManager.java create mode 100644 examples/tv-app/android/java/src/com/matter/tv/server/tvapp/LauncherResponse.java 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 9a359a0e4ea919..1444915c368b6b 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 efef32a9cfc1c9..b72a4b280351b9 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 529816928b2f3a..41421c7e917d0e 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 lastReceivedInstallationStatus = + new HashMap<>(); + + private LiveData installStateLiveData; + + public ApplicationLauncherManagerImpl(Context context) { + packageManager = context.getPackageManager(); + endpointsDataStore = EndpointsDataStore.getInstance(context); + registerSelf(context); + } + + private final Observer 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 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 getInstalledPackages(PackageManager packageManager) { + List packageInfoList = packageManager.getInstalledPackages(0); + Set setOfInstalledApps = new HashSet<>(); + for (PackageInfo info : packageInfoList) { + setOfInstalledApps.add(info.packageName); + } + return setOfInstalledApps; + } + + public static LiveData installationStates(Context context) { + MutableLiveData liveData = new MutableLiveData<>(); + PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); + Map 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 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 & 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 & 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 & 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 069d0f5185e62f..d7da8ab6661a46 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 #include #include @@ -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 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 #include @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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(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(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 & 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(data.data())); + + chip::UtfString jappId(env, application.applicationID); + + // Create an instance of Application + jobject appObject = env->NewObject(mApplicationClass, mCreateApplicationMethod, + static_cast(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(status); + response.data = chip::Optional(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 & 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(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(status); + response.data = chip::Optional(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 & 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(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(status); + response.data = chip::Optional(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(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, "", "(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 -#include + +#include +#include 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 & helper, const ApplicationType & application) override; void HandleHideApp(CommandResponseHelper & 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 da5314f380e607..89e8670d9bbad3 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. + + + diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index 158ba6d35781f4..2e41cad5a77fe2 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -9124,6 +9124,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 774b76e886af4a..405d8b8872bcba 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -45606,11 +45606,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 c9850dd3cfd50f..9e864b458de94d 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h @@ -21067,6 +21067,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 #include +#include #include #include #include @@ -50,6 +51,8 @@ class JniUtfString chip::CharSpan charSpan() const { return chip::CharSpan(c_str(), static_cast(size())); } + chip::ByteSpan byteSpan() const { return chip::ByteSpan(chip::Uint8::from_const_char(c_str()), static_cast(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 b7305aa2cbfdab..da2047e745d586 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 @@ -3426,6 +3426,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 b7986208f1dca8..a6121b3681348e 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 @@ -5220,14 +5220,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