/** * * Copyright (c) 2021 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /**************************************************************************** * @file * @brief Routines for the Application Launcher plugin, the *server implementation of the Application Launcher cluster. ******************************************************************************* ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED #include #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED using namespace chip; using namespace chip::app::Clusters; using namespace chip::app::Clusters::ApplicationLauncher; #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED using namespace chip::AppPlatform; #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED using namespace chip::Uint8; static constexpr size_t kApplicationLauncherDelegateTableSize = MATTER_DM_APPLICATION_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; static_assert(kApplicationLauncherDelegateTableSize <= kEmberInvalidEndpointIndex, "ApplicationLauncher Delegate table size error"); // ----------------------------------------------------------------------------- // Delegate Implementation using chip::app::Clusters::ApplicationBasic::CatalogVendorApp; using chip::app::Clusters::ApplicationLauncher::Delegate; using ApplicationStatusEnum = app::Clusters::ApplicationBasic::ApplicationStatusEnum; using chip::Protocols::InteractionModel::Status; namespace { Delegate * gDelegateTable[kApplicationLauncherDelegateTableSize] = { nullptr }; Delegate * GetDelegate(EndpointId endpoint) { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(endpoint); if (app != nullptr) { ChipLogProgress(Zcl, "ApplicationLauncher returning ContentApp delegate for endpoint:%u", endpoint); return app->GetApplicationLauncherDelegate(); } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ChipLogProgress(Zcl, "ApplicationLauncher NOT returning ContentApp delegate for endpoint:%u", endpoint); uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, ApplicationLauncher::Id, MATTER_DM_APPLICATION_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT); return (ep >= kApplicationLauncherDelegateTableSize ? nullptr : gDelegateTable[ep]); } bool isDelegateNull(Delegate * delegate, EndpointId endpoint) { if (delegate == nullptr) { ChipLogProgress(Zcl, "Application Launcher has no delegate set for endpoint:%u", endpoint); return true; } return false; } } // namespace namespace chip { namespace app { namespace Clusters { namespace ApplicationLauncher { void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) { uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, ApplicationLauncher::Id, MATTER_DM_APPLICATION_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT); // if endpoint is found if (ep < kApplicationLauncherDelegateTableSize) { gDelegateTable[ep] = delegate; } else { } } bool HasFeature(chip::EndpointId endpoint, Feature feature) { bool hasFeature = false; uint32_t featureMap = 0; Status status = Attributes::FeatureMap::Get(endpoint, &featureMap); if (Status::Success == status) { hasFeature = (featureMap & chip::to_underlying(feature)); } return hasFeature; } // this attribute should only be enabled for app platform instance (endpoint 1) CHIP_ERROR Delegate::HandleGetCurrentApp(app::AttributeValueEncoder & aEncoder) { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED if (HasFeature(Feature::kApplicationPlatform)) { auto & platform = ContentAppPlatform::GetInstance(); if (platform.HasCurrentApp()) { ContentApp * app = platform.GetContentApp(platform.GetCurrentAppEndpointId()); if (app != nullptr) { ApplicationEPType currentApp; CatalogVendorApp * vendorApp = app->GetApplicationBasicDelegate()->GetCatalogVendorApp(); currentApp.application.catalogVendorID = vendorApp->catalogVendorId; currentApp.application.applicationID = CharSpan(vendorApp->applicationId, strlen(vendorApp->applicationId)); currentApp.endpoint = Optional(app->GetEndpointId()); return aEncoder.Encode(currentApp); } } } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED return aEncoder.EncodeNull(); } } // namespace ApplicationLauncher } // namespace Clusters } // namespace app } // namespace chip // ----------------------------------------------------------------------------- // Attribute Accessor Implementation namespace { class ApplicationLauncherAttrAccess : public app::AttributeAccessInterface { public: ApplicationLauncherAttrAccess() : app::AttributeAccessInterface(Optional::Missing(), ApplicationLauncher::Id) {} CHIP_ERROR Read(const app::ConcreteReadAttributePath & aPath, app::AttributeValueEncoder & aEncoder) override; private: CHIP_ERROR ReadCatalogListAttribute(app::AttributeValueEncoder & aEncoder, Delegate * delegate); CHIP_ERROR ReadCurrentAppAttribute(app::AttributeValueEncoder & aEncoder, Delegate * delegate); }; ApplicationLauncherAttrAccess gApplicationLauncherAttrAccess; CHIP_ERROR ApplicationLauncherAttrAccess::Read(const app::ConcreteReadAttributePath & aPath, app::AttributeValueEncoder & aEncoder) { EndpointId endpoint = aPath.mEndpointId; Delegate * delegate = GetDelegate(endpoint); switch (aPath.mAttributeId) { case app::Clusters::ApplicationLauncher::Attributes::CatalogList::Id: { if (isDelegateNull(delegate, endpoint)) { return aEncoder.EncodeEmptyList(); } return ReadCatalogListAttribute(aEncoder, delegate); } case app::Clusters::ApplicationLauncher::Attributes::CurrentApp::Id: { if (isDelegateNull(delegate, endpoint)) { return CHIP_NO_ERROR; } return ReadCurrentAppAttribute(aEncoder, delegate); } default: { break; } } return CHIP_NO_ERROR; } CHIP_ERROR ApplicationLauncherAttrAccess::ReadCatalogListAttribute(app::AttributeValueEncoder & aEncoder, Delegate * delegate) { return delegate->HandleGetCatalogList(aEncoder); } CHIP_ERROR ApplicationLauncherAttrAccess::ReadCurrentAppAttribute(app::AttributeValueEncoder & aEncoder, Delegate * delegate) { return delegate->HandleGetCurrentApp(aEncoder); } } // anonymous namespace // ----------------------------------------------------------------------------- // Matter Framework Callbacks Implementation bool emberAfApplicationLauncherClusterLaunchAppCallback(app::CommandHandler * command, const app::ConcreteCommandPath & commandPath, const Commands::LaunchApp::DecodableType & commandData) { CHIP_ERROR err = CHIP_NO_ERROR; EndpointId endpoint = commandPath.mEndpointId; auto & data = commandData.data; auto & application = commandData.application; app::CommandResponseHelper responder(command, commandPath); // TODO: We should be able to not have an `application` if the AP feature is // off: https://github.com/project-chip/connectedhomeip/issues/24595 if (!application.HasValue()) { command->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } std::string appId(application.Value().applicationID.data(), application.Value().applicationID.size()); CatalogVendorApp vendorApp(application.Value().catalogVendorID, appId.c_str()); Delegate * delegate = GetDelegate(endpoint); VerifyOrExit(isDelegateNull(delegate, endpoint) != true, err = CHIP_ERROR_INCORRECT_STATE); { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // if the feature flag is APP_Plaform, then this is a call to endpoint 1 to launching an app // 1. Find target Content App and load if not loaded // 2. Set current app to Content App // 3. Set Content App status (basic cluster) to ACTIVE_VISIBLE_FOCUS // 4. Call launch app command on Content App if (delegate->HasFeature(Feature::kApplicationPlatform)) { ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature"); ContentApp * app = ContentAppPlatform::GetInstance().LoadContentApp(&vendorApp); if (app == nullptr) { ChipLogError(Zcl, "ApplicationLauncher target app not found"); LauncherResponseType response; response.status = StatusEnum::kAppNotAvailable; responder.Success(response); return true; } ContentAppPlatform::GetInstance().SetCurrentApp(app); ChipLogProgress(Zcl, "ApplicationLauncher handling launch on ContentApp"); app->GetApplicationLauncherDelegate()->HandleLaunchApp(responder, data.HasValue() ? data.Value() : ByteSpan(), application.Value()); return true; } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // otherwise, assume this is for managing status of the Content App on this endpoint // 1. Set Content App status (basic cluster) to ACTIVE_VISIBLE_FOCUS // 2. Call launch app command on the given endpoint ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature"); ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint); if (appBasic != nullptr) { ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to visible"); appBasic->SetApplicationStatus(ApplicationStatusEnum::kActiveVisibleFocus); } #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentApp * app = ContentAppPlatform::GetInstance().LoadContentApp(&vendorApp); if (app != nullptr) { ContentAppPlatform::GetInstance().SetCurrentApp(app); } else if (delegate->HasFeature(Feature::kApplicationPlatform)) { ChipLogProgress(Zcl, "ApplicationLauncher target app not found"); LauncherResponseType response; response.status = StatusEnum::kAppNotAvailable; responder.Success(response); return true; } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ChipLogProgress(Zcl, "ApplicationLauncher handling launch"); delegate->HandleLaunchApp(responder, data.HasValue() ? data.Value() : ByteSpan(), application.Value()); } exit: if (err != CHIP_NO_ERROR) { ChipLogError(Zcl, "emberAfApplicationLauncherClusterLaunchAppCallback error: %s", err.AsString()); command->AddStatus(commandPath, Status::Failure); } return true; } /** * @brief Application Launcher Cluster StopApp Command callback (from client) */ bool emberAfApplicationLauncherClusterStopAppCallback(app::CommandHandler * command, const app::ConcreteCommandPath & commandPath, const Commands::StopApp::DecodableType & commandData) { CHIP_ERROR err = CHIP_NO_ERROR; EndpointId endpoint = commandPath.mEndpointId; auto & application = commandData.application; app::CommandResponseHelper responder(command, commandPath); // TODO: We should be able to not have an `application` if the AP feature is // off: https://github.com/project-chip/connectedhomeip/issues/24595 if (!application.HasValue()) { command->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } std::string appId(application.Value().applicationID.data(), application.Value().applicationID.size()); CatalogVendorApp vendorApp(application.Value().catalogVendorID, appId.c_str()); Delegate * delegate = GetDelegate(endpoint); VerifyOrExit(isDelegateNull(delegate, endpoint) != true, err = CHIP_ERROR_INCORRECT_STATE); { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // if the feature flag is APP_Plaform, then this is a call to endpoint 1 to stop an app // 1. Find target Content App // 3. If this was the current app then stop it // 2. Set Content App status (basic cluster) to ACTIVE_STOPPED // 4. Call stop app command on Content App if (delegate->HasFeature(Feature::kApplicationPlatform)) { ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature"); ContentApp * app = ContentAppPlatform::GetInstance().LoadContentApp(&vendorApp); if (app == nullptr) { ChipLogError(Zcl, "ApplicationLauncher target app not loaded"); LauncherResponseType response; response.status = StatusEnum::kAppNotAvailable; responder.Success(response); return true; } ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app); ChipLogProgress(Zcl, "ApplicationLauncher setting app status"); app->GetApplicationBasicDelegate()->SetApplicationStatus(ApplicationStatusEnum::kStopped); ChipLogProgress(Zcl, "ApplicationLauncher handling stop on ContentApp"); app->GetApplicationLauncherDelegate()->HandleStopApp(responder, application.Value()); return true; } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // otherwise, assume this is for managing status of the Content App on this endpoint // 1. Set Content App status (basic cluster) to ACTIVE_STOPPED // 2. Call launch app command on the given endpoint ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature"); #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp); if (app != nullptr) { ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app); app->GetApplicationBasicDelegate()->SetApplicationStatus(ApplicationStatusEnum::kStopped); } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint); if (appBasic != nullptr) { ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to stopped"); appBasic->SetApplicationStatus(ApplicationStatusEnum::kStopped); } delegate->HandleStopApp(responder, application.Value()); } exit: if (err != CHIP_NO_ERROR) { ChipLogError(Zcl, "emberAfApplicationLauncherClusterStopAppCallback error: %s", err.AsString()); command->AddStatus(commandPath, Status::Failure); } return true; } /** * @brief Application Launcher Cluster HideApp Command callback (from client) */ bool emberAfApplicationLauncherClusterHideAppCallback(app::CommandHandler * command, const app::ConcreteCommandPath & commandPath, const Commands::HideApp::DecodableType & commandData) { CHIP_ERROR err = CHIP_NO_ERROR; EndpointId endpoint = commandPath.mEndpointId; auto & application = commandData.application; app::CommandResponseHelper responder(command, commandPath); // TODO: We should be able to not have an `application` if the AP feature is // off: https://github.com/project-chip/connectedhomeip/issues/24595 if (!application.HasValue()) { command->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } std::string appId(application.Value().applicationID.data(), application.Value().applicationID.size()); CatalogVendorApp vendorApp(application.Value().catalogVendorID, appId.c_str()); Delegate * delegate = GetDelegate(endpoint); VerifyOrExit(isDelegateNull(delegate, endpoint) != true, err = CHIP_ERROR_INCORRECT_STATE); { #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // if the feature flag is APP_Plaform, then this is a call to endpoint 1 to stop an app // 1. Find target Content App // 3. If this was the current app then hide it // 2. Set Content App status (basic cluster) to ACTIVE_VISIBLE_NOT_FOCUS // 4. Call stop app command on Content App if (delegate->HasFeature(Feature::kApplicationPlatform)) { ChipLogProgress(Zcl, "ApplicationLauncher has content platform feature"); ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp); if (app == nullptr) { ChipLogError(Zcl, "ApplicationLauncher target app not loaded"); LauncherResponseType response; response.status = StatusEnum::kAppNotAvailable; responder.Success(response); return true; } ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app); ChipLogProgress(Zcl, "ApplicationLauncher handling stop on ContentApp"); app->GetApplicationLauncherDelegate()->HandleHideApp(responder, application.Value()); return true; } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED // otherwise, assume this is for managing status of the Content App on this endpoint // 1. Set Content App status (basic cluster) to ACTIVE_VISIBLE_NOT_FOCUS // 2. Call launch app command on the given endpoint ChipLogProgress(Zcl, "ApplicationLauncher no content platform feature"); #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(&vendorApp); if (app != nullptr) { ContentAppPlatform::GetInstance().UnsetIfCurrentApp(app); } #endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ApplicationBasic::Delegate * appBasic = ApplicationBasic::GetDefaultDelegate(endpoint); if (appBasic != nullptr) { ChipLogProgress(Zcl, "ApplicationLauncher setting basic cluster status to stopped"); appBasic->SetApplicationStatus(ApplicationStatusEnum::kActiveHidden); } delegate->HandleHideApp(responder, application.Value()); } exit: if (err != CHIP_NO_ERROR) { ChipLogError(Zcl, "emberAfApplicationLauncherClusterStopAppCallback error: %s", err.AsString()); command->AddStatus(commandPath, Status::Failure); } return true; } // ----------------------------------------------------------------------------- // Plugin initialization void MatterApplicationLauncherPluginServerInitCallback() { app::AttributeAccessInterfaceRegistry::Instance().Register(&gApplicationLauncherAttrAccess); }