From 78b6ae507fa3a798a8f9b1141ed6feb2c7c40f0a Mon Sep 17 00:00:00 2001 From: Lazar Kovacic Date: Wed, 22 May 2024 18:40:01 +0200 Subject: [PATCH] Add basic support for App Install Flow (#33445) * Add logic for the basic app installation flow * Add more logic * Update basic logic * Update codebase * Update code * Update code * Add missing content apps * Update TODO comment * Update code per comments * Update Logic per comments * Update code to make android work * Update per comments * Restyled by whitespace * Restyled by clang-format * Restyled by prettier-markdown --------- Co-authored-by: Restyled.io --- .../android/java/MyUserPrompter-JNI.cpp | 9 ++ .../tv-app/android/java/MyUserPrompter-JNI.h | 1 + examples/tv-app/linux/README.md | 5 + examples/tv-app/tv-common/include/AppTv.h | 17 ++-- .../tv-common/shell/AppTvShellCommands.cpp | 49 ++++++++++ examples/tv-app/tv-common/src/AppTv.cpp | 93 +++++++++++++++++-- .../CommissionerDiscoveryController.cpp | 30 ++++++ .../CommissionerDiscoveryController.h | 45 ++++++++- 8 files changed, 235 insertions(+), 14 deletions(-) diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp index 6d586d99c1c67f..4eb0abe25645da 100644 --- a/examples/tv-app/android/java/MyUserPrompter-JNI.cpp +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.cpp @@ -227,6 +227,15 @@ bool JNIMyUserPrompter::DisplaysPasscodeAndQRCode() return false; } +/** + * Called to prompt the user for consent to allow the app commissioneeName/vendorId/productId to be installed. + * For example "[commissioneeName] is requesting permission to install app to this TV, approve?" + */ +void JNIMyUserPrompter::PromptForAppInstallOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) +{ + ChipLogError(Zcl, "JNIMyUserPrompter::PromptForAppInstallOKPermission Needs Implementation"); +} + /** * Called to display the given setup passcode to the user, * for commissioning the given commissioneeName with the given vendorId and productId, diff --git a/examples/tv-app/android/java/MyUserPrompter-JNI.h b/examples/tv-app/android/java/MyUserPrompter-JNI.h index 408346326a6fd6..3d2e7f14afb75e 100644 --- a/examples/tv-app/android/java/MyUserPrompter-JNI.h +++ b/examples/tv-app/android/java/MyUserPrompter-JNI.h @@ -29,6 +29,7 @@ class JNIMyUserPrompter : public UserPrompter void PromptForCommissionOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; void PromptForCommissionPasscode(uint16_t vendorId, uint16_t productId, const char * commissioneeName, uint16_t pairingHint, const char * pairingInstruction) override; + void PromptForAppInstallOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; void HidePromptsOnCancel(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override; bool DisplaysPasscodeAndQRCode() override; void PromptWithCommissionerPasscode(uint16_t vendorId, uint16_t productId, const char * commissioneeName, uint32_t passcode, diff --git a/examples/tv-app/linux/README.md b/examples/tv-app/linux/README.md index 5cd2bcfc48b5ed..2fc85a9a8f25c8 100644 --- a/examples/tv-app/linux/README.md +++ b/examples/tv-app/linux/README.md @@ -107,6 +107,11 @@ id): $ app add 9050 (vendor id 9050) $ app remove 1 +You can also install or uninstall the app by using commands: + + $ app install 65521 32768 + $ app uninstall 65521 32768 + As an app platform, local apps can be used to facilitate commissioning using their AccountLogin clusters. The dummy apps have hardcoded setup codes - on a real device, these apps would communicate with a cloud service to obtain the diff --git a/examples/tv-app/tv-common/include/AppTv.h b/examples/tv-app/tv-common/include/AppTv.h index bd056ae6813a5f..34f5bd8dc4c958 100644 --- a/examples/tv-app/tv-common/include/AppTv.h +++ b/examples/tv-app/tv-common/include/AppTv.h @@ -91,6 +91,11 @@ class DLL_EXPORT ContentAppImpl : public ContentApp KeypadInputDelegate * GetKeypadInputDelegate() override { return &mKeypadInputDelegate; }; MediaPlaybackDelegate * GetMediaPlaybackDelegate() override { return &mMediaPlaybackDelegate; }; TargetNavigatorDelegate * GetTargetNavigatorDelegate() override { return &mTargetNavigatorDelegate; }; + bool MatchesPidVid(uint16_t productId, uint16_t vendorId) + { + return vendorId == mApplicationBasicDelegate.HandleGetVendorId() && + productId == mApplicationBasicDelegate.HandleGetProductId(); + } protected: ApplicationBasicManager mApplicationBasicDelegate; @@ -138,15 +143,13 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory uint16_t productId) override; void AddAdminVendorId(uint16_t vendorId); + // Add the app to the list of mContentApps + void InstallContentApp(uint16_t vendorId, uint16_t productId); + // Remove the app from the list of mContentApps + bool UninstallContentApp(uint16_t vendorId, uint16_t productId); protected: - ContentAppImpl mContentApps[APP_LIBRARY_SIZE] = { - ContentAppImpl("Vendor1", 1, "exampleid", 11, "Version1", "34567890"), - ContentAppImpl("Vendor2", 65521, "exampleString", 32768, "Version2", "20202021"), - ContentAppImpl("Vendor3", 9050, "App3", 22, "Version3", "20202021"), - ContentAppImpl("TestSuiteVendor", 1111, "applicationId", 22, "v2", "20202021") - }; - + std::vector> mContentApps; std::vector mAdminVendorIds{}; }; diff --git a/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp b/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp index ee85cf099ff380..b39e84ad40f89a 100644 --- a/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp +++ b/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp @@ -243,6 +243,55 @@ static CHIP_ERROR AppPlatformHandler(int argc, char ** argv) return CHIP_NO_ERROR; } + else if (strcmp(argv[0], "install") == 0) + { + if (argc < 2) + { + return PrintAllCommands(); + } + char * eptr; + + uint16_t vid = (uint16_t) strtol(argv[1], &eptr, 10); + uint16_t pid = 0; + if (argc >= 3) + { + pid = (uint16_t) strtol(argv[2], &eptr, 10); + } + ContentAppFactoryImpl * factory = GetContentAppFactoryImpl(); + factory->InstallContentApp(vid, pid); + + ChipLogProgress(DeviceLayer, "installed an app"); + + return CHIP_NO_ERROR; + } + else if (strcmp(argv[0], "uninstall") == 0) + { + if (argc < 2) + { + return PrintAllCommands(); + } + char * eptr; + + uint16_t vid = (uint16_t) strtol(argv[1], &eptr, 10); + uint16_t pid = 0; + if (argc >= 3) + { + pid = (uint16_t) strtol(argv[2], &eptr, 10); + } + ContentAppFactoryImpl * factory = GetContentAppFactoryImpl(); + bool isAppUninstalled = factory->UninstallContentApp(vid, pid); + + if (isAppUninstalled) + { + ChipLogProgress(DeviceLayer, "uninstalled an app"); + } + else + { + ChipLogProgress(DeviceLayer, "app not found."); + } + + return CHIP_NO_ERROR; + } else if (strcmp(argv[0], "add") == 0) { if (argc < 2) diff --git a/examples/tv-app/tv-common/src/AppTv.cpp b/examples/tv-app/tv-common/src/AppTv.cpp index 2bed28e34d5fb8..9987b4cdd67d6e 100644 --- a/examples/tv-app/tv-common/src/AppTv.cpp +++ b/examples/tv-app/tv-common/src/AppTv.cpp @@ -98,6 +98,12 @@ class MyUserPrompter : public UserPrompter // tv should override this with a dialog prompt inline void PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error) override { return; } + + // tv should override this with a dialog prompt + inline void PromptForAppInstallOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) override + { + return; + } }; MyUserPrompter gMyUserPrompter; @@ -146,6 +152,16 @@ class MyPasscodeService : public PasscodeService }; MyPasscodeService gMyPasscodeService; +class MyAppInstallationService : public AppInstallationService +{ + bool LookupTargetContentApp(uint16_t vendorId, uint16_t productId) override + { + return ContentAppPlatform::GetInstance().LoadContentAppByClient(vendorId, productId) != nullptr; + } +}; + +MyAppInstallationService gMyAppInstallationService; + class MyPostCommissioningListener : public PostCommissioningListener { void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, @@ -527,19 +543,23 @@ ContentApp * ContentAppFactoryImpl::LoadContentApp(const CatalogVendorApp & vend { ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl: LoadContentAppByAppId catalogVendorId=%d applicationId=%s ", vendorApp.catalogVendorId, vendorApp.applicationId); + int index = 0; - for (size_t i = 0; i < ArraySize(mContentApps); ++i) + for (auto & contentApp : mContentApps) { - auto & app = mContentApps[i]; - ChipLogProgress(DeviceLayer, " Looking next=%s ", app.GetApplicationBasicDelegate()->GetCatalogVendorApp()->applicationId); - if (app.GetApplicationBasicDelegate()->GetCatalogVendorApp()->Matches(vendorApp)) + auto app = contentApp.get(); + + ChipLogProgress(DeviceLayer, " Looking next=%s ", app->GetApplicationBasicDelegate()->GetCatalogVendorApp()->applicationId); + if (app->GetApplicationBasicDelegate()->GetCatalogVendorApp()->Matches(vendorApp)) { - ContentAppPlatform::GetInstance().AddContentApp(&app, &contentAppEndpoint, Span(gDataVersions[i]), + ContentAppPlatform::GetInstance().AddContentApp(app, &contentAppEndpoint, Span(gDataVersions[index]), Span(gContentAppDeviceType)); - return &app; + return app; } + index++; } + ChipLogProgress(DeviceLayer, "LoadContentAppByAppId NOT FOUND catalogVendorId=%d applicationId=%s ", vendorApp.catalogVendorId, vendorApp.applicationId); @@ -551,6 +571,62 @@ void ContentAppFactoryImpl::AddAdminVendorId(uint16_t vendorId) mAdminVendorIds.push_back(vendorId); } +void ContentAppFactoryImpl::InstallContentApp(uint16_t vendorId, uint16_t productId) +{ + ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl: InstallContentApp vendorId=%d productId=%d ", vendorId, productId); + if (vendorId == 1 && productId == 11) + { + mContentApps.emplace_back( + std::make_unique("Vendor1", vendorId, "exampleid", productId, "Version1", "34567890")); + } + else if (vendorId == 65521 && productId == 32768) + { + mContentApps.emplace_back( + std::make_unique("Vendor2", vendorId, "exampleString", productId, "Version2", "20202021")); + } + else if (vendorId == 9050 && productId == 22) + { + mContentApps.emplace_back(std::make_unique("Vendor3", vendorId, "App3", productId, "Version3", "20202021")); + } + else if (vendorId == 1111 && productId == 22) + { + mContentApps.emplace_back( + std::make_unique("TestSuiteVendor", vendorId, "applicationId", productId, "v2", "20202021")); + } + else + { + mContentApps.emplace_back( + std::make_unique("NewAppVendor", vendorId, "newAppApplicationId", productId, "v2", "20202021")); + } +} + +bool ContentAppFactoryImpl::UninstallContentApp(uint16_t vendorId, uint16_t productId) +{ + ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl: UninstallContentApp vendorId=%d productId=%d ", vendorId, productId); + + int index = 0; + for (auto & contentApp : mContentApps) + { + + auto app = contentApp.get(); + + ChipLogProgress(DeviceLayer, "Looking next vid=%d pid=%d", app->GetApplicationBasicDelegate()->HandleGetVendorId(), + app->GetApplicationBasicDelegate()->HandleGetProductId()); + + if (app->MatchesPidVid(productId, vendorId)) + { + ChipLogProgress(DeviceLayer, "Found an app vid=%d pid=%d. Uninstalling it.", + app->GetApplicationBasicDelegate()->HandleGetVendorId(), + app->GetApplicationBasicDelegate()->HandleGetProductId()); + mContentApps.erase(mContentApps.begin() + index); + return true; + } + + index++; + } + return false; +} + Access::Privilege ContentAppFactoryImpl::GetVendorPrivilege(uint16_t vendorId) { for (size_t i = 0; i < mAdminVendorIds.size(); ++i) @@ -607,6 +683,10 @@ CHIP_ERROR AppTvInit() #if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED ContentAppPlatform::GetInstance().SetupAppPlatform(); ContentAppPlatform::GetInstance().SetContentAppFactory(&gFactory); + gFactory.InstallContentApp((uint16_t) 1, (uint16_t) 11); + gFactory.InstallContentApp((uint16_t) 65521, (uint16_t) 32768); + gFactory.InstallContentApp((uint16_t) 9050, (uint16_t) 22); + gFactory.InstallContentApp((uint16_t) 1111, (uint16_t) 22); uint16_t value; if (DeviceLayer::GetDeviceInstanceInfoProvider()->GetVendorId(value) != CHIP_NO_ERROR) { @@ -623,6 +703,7 @@ CHIP_ERROR AppTvInit() if (cdc != nullptr) { cdc->SetPasscodeService(&gMyPasscodeService); + cdc->SetAppInstallationService(&gMyAppInstallationService); cdc->SetUserPrompter(&gMyUserPrompter); cdc->SetPostCommissioningListener(&gMyPostCommissioningListener); } diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp index 4df133c59e99f3..d1a5747491ad50 100644 --- a/src/controller/CommissionerDiscoveryController.cpp +++ b/src/controller/CommissionerDiscoveryController.cpp @@ -219,6 +219,34 @@ void CommissionerDiscoveryController::InternalOk() ChipLogError(AppServer, "UX InternalOk: could not find instance=%s", mCurrentInstance); return; } + + if (mAppInstallationService == nullptr) + { + ChipLogError(AppServer, "UX InternalOk: no app installation service"); + return; + } + + if (!mAppInstallationService->LookupTargetContentApp(client->GetVendorId(), client->GetProductId())) + { + ChipLogDetail(AppServer, "UX InternalOk: app not installed."); + + // notify client that app will be installed + CommissionerDeclaration cd; + cd.SetErrorCode(CommissionerDeclaration::CdError::kAppInstallConsentPending); + mUdcServer->SendCDCMessage(cd, Transport::PeerAddress::UDP(client->GetPeerAddress().GetIPAddress(), client->GetCdPort())); + + // dialog + ChipLogDetail(Controller, "------PROMPT USER: %s is requesting to install app on this TV. vendorId=%d, productId=%d", + client->GetDeviceName(), client->GetVendorId(), client->GetProductId()); + + if (mUserPrompter != nullptr) + { + mUserPrompter->PromptForAppInstallOKPermission(client->GetVendorId(), client->GetProductId(), client->GetDeviceName()); + } + ChipLogDetail(Controller, "------Via Shell Enter: app install "); + return; + } + if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kPromptingUser) { ChipLogError(AppServer, "UX InternalOk: invalid state for ok"); @@ -244,6 +272,7 @@ void CommissionerDiscoveryController::InternalOk() CharSpan rotatingIdSpan(rotatingIdBuffer, 2 * rotatingIdLength); uint8_t targetAppCount = client->GetNumTargetAppInfos(); + if (targetAppCount > 0) { ChipLogDetail(AppServer, "UX InternalOk: checking for each target app specified"); @@ -583,6 +612,7 @@ void CommissionerDiscoveryController::Cancel() } client->SetUDCClientProcessingState(UDCClientProcessingState::kUserDeclined); mPendingConsent = false; + ResetState(); } void CommissionerDiscoveryController::CommissioningSucceeded(uint16_t vendorId, uint16_t productId, NodeId nodeId, diff --git a/src/controller/CommissionerDiscoveryController.h b/src/controller/CommissionerDiscoveryController.h index 521fe5c611dc3c..b2211641fd0ede 100644 --- a/src/controller/CommissionerDiscoveryController.h +++ b/src/controller/CommissionerDiscoveryController.h @@ -150,6 +150,21 @@ class DLL_EXPORT UserPrompter */ virtual void PromptCommissioningFailed(const char * commissioneeName, CHIP_ERROR error) = 0; + /** + * @brief + * Called to prompt the user for consent to allow the app commissioneeName/vendorId/productId to be installed. + * For example "[commissioneeName] is requesting permission to install app to this TV, approve?" + * + * If user responds with OK then implementor should call CommissionerRespondOk(); + * If user responds with Cancel then implementor should call CommissionerRespondCancel(); + * + * @param[in] vendorId The vendorId in the DNS-SD advertisement of the requesting commissionee. + * @param[in] productId The productId in the DNS-SD advertisement of the requesting commissionee. + * @param[in] commissioneeName The commissioneeName in the DNS-SD advertisement of the requesting commissionee. + * + */ + virtual void PromptForAppInstallOKPermission(uint16_t vendorId, uint16_t productId, const char * commissioneeName) = 0; + virtual ~UserPrompter() = default; }; @@ -158,7 +173,7 @@ class DLL_EXPORT PasscodeService public: /** * @brief - * Called to determine if the given target app is available to the commissionee with the given given + * Called to determine if the given target app is available to the commissionee with the given * vendorId/productId, and if so, return the passcode. * * This will be called by the main chip thread so any blocking work should be moved to a separate thread. @@ -204,6 +219,25 @@ class DLL_EXPORT PasscodeService virtual ~PasscodeService() = default; }; +class DLL_EXPORT AppInstallationService +{ +public: + /** + * @brief + * Called to check if the given target app is available to the commissione with th given + * vendorId/productId + * + * This will be called by the main chip thread so any blocking work should be moved to a separate thread. + * + * @param[in] vendorId The vendorId in the DNS-SD advertisement of the requesting commissionee. + * @param[in] productId The productId in the DNS-SD advertisement of the requesting commissionee. + * + */ + virtual bool LookupTargetContentApp(uint16_t vendorId, uint16_t productId) = 0; + + virtual ~AppInstallationService() = default; +}; + class DLL_EXPORT PostCommissioningListener { public: @@ -392,6 +426,14 @@ class CommissionerDiscoveryController : public chip::Protocols::UserDirectedComm inline void SetPasscodeService(PasscodeService * passcodeService) { mPasscodeService = passcodeService; } inline PasscodeService * GetPasscodeService() { return mPasscodeService; } + /** + * Assign an AppInstallationService + */ + inline void SetAppInstallationService(AppInstallationService * appInstallationService) + { + mAppInstallationService = appInstallationService; + } + /** * Assign a Commissioner Callback to perform commissioning once user consent has been given */ @@ -430,6 +472,7 @@ class CommissionerDiscoveryController : public chip::Protocols::UserDirectedComm UserDirectedCommissioningServer * mUdcServer = nullptr; UserPrompter * mUserPrompter = nullptr; PasscodeService * mPasscodeService = nullptr; + AppInstallationService * mAppInstallationService = nullptr; CommissionerCallback * mCommissionerCallback = nullptr; PostCommissioningListener * mPostCommissioningListener = nullptr; };