Skip to content

Commit

Permalink
Add basic support for App Install Flow (#33445)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Nov 15, 2024
1 parent 1f13abe commit 1d6853c
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 14 deletions.
9 changes: 9 additions & 0 deletions examples/tv-app/android/java/MyUserPrompter-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions examples/tv-app/android/java/MyUserPrompter-JNI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions examples/tv-app/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 10 additions & 7 deletions examples/tv-app/tv-common/include/AppTv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<std::unique_ptr<ContentAppImpl>> mContentApps;
std::vector<uint16_t> mAdminVendorIds{};
};

Expand Down
49 changes: 49 additions & 0 deletions examples/tv-app/tv-common/shell/AppTvShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
93 changes: 87 additions & 6 deletions examples/tv-app/tv-common/src/AppTv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<DataVersion>(gDataVersions[i]),
ContentAppPlatform::GetInstance().AddContentApp(app, &contentAppEndpoint, Span<DataVersion>(gDataVersions[index]),
Span<const EmberAfDeviceType>(gContentAppDeviceType));
return &app;
return app;
}
index++;
}

ChipLogProgress(DeviceLayer, "LoadContentAppByAppId NOT FOUND catalogVendorId=%d applicationId=%s ", vendorApp.catalogVendorId,
vendorApp.applicationId);

Expand All @@ -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<ContentAppImpl>("Vendor1", vendorId, "exampleid", productId, "Version1", "34567890"));
}
else if (vendorId == 65521 && productId == 32768)
{
mContentApps.emplace_back(
std::make_unique<ContentAppImpl>("Vendor2", vendorId, "exampleString", productId, "Version2", "20202021"));
}
else if (vendorId == 9050 && productId == 22)
{
mContentApps.emplace_back(std::make_unique<ContentAppImpl>("Vendor3", vendorId, "App3", productId, "Version3", "20202021"));
}
else if (vendorId == 1111 && productId == 22)
{
mContentApps.emplace_back(
std::make_unique<ContentAppImpl>("TestSuiteVendor", vendorId, "applicationId", productId, "v2", "20202021"));
}
else
{
mContentApps.emplace_back(
std::make_unique<ContentAppImpl>("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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -623,6 +703,7 @@ CHIP_ERROR AppTvInit()
if (cdc != nullptr)
{
cdc->SetPasscodeService(&gMyPasscodeService);
cdc->SetAppInstallationService(&gMyAppInstallationService);
cdc->SetUserPrompter(&gMyUserPrompter);
cdc->SetPostCommissioningListener(&gMyPostCommissioningListener);
}
Expand Down
30 changes: 30 additions & 0 deletions src/controller/CommissionerDiscoveryController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pid> <vid>");
return;
}

if (client->GetUDCClientProcessingState() != UDCClientProcessingState::kPromptingUser)
{
ChipLogError(AppServer, "UX InternalOk: invalid state for ok");
Expand All @@ -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");
Expand Down Expand Up @@ -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,
Expand Down
45 changes: 44 additions & 1 deletion src/controller/CommissionerDiscoveryController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
};
Expand Down

0 comments on commit 1d6853c

Please sign in to comment.