Skip to content

Commit

Permalink
Add SupportedCluster list to ContentAppPlatform
Browse files Browse the repository at this point in the history
[Problem]
The ContentAppPlatform does not know which clusters each ContentApp supports.

Currently the ContentApp is queried to get the passcode as long as the
AccountLoginDelegate is present whether or not the installed ContentApp
has declared support for AccountLogin cluster in its manifest.

[Solution]
Extend the native ContentApp with a SupportedCluster list. The list is
initialized from the Android AppPlatformService whenever an installed ContentApp
is discovered and added to the native AppPlatform.

This list is used to check if AccountLogin cluster is supported before
querying the ContentApp for the passcode.

[Test]
The feature is tested end-to-end using a native Linux casting-app and
and an Android platform-app and content-app. The content-app
static_matter_clusters raw asset was manipulated to verify that the
clusters are parsed correctly and that the passcode is only retrievable
when the AccountLogin cluster is declared.
  • Loading branch information
mthiesc committed Jun 13, 2024
1 parent a3bb9c3 commit 1084960
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@
import android.content.IntentFilter;
import android.util.Log;
import androidx.annotation.NonNull;
import com.matter.tv.app.api.SupportedCluster;
import com.matter.tv.server.handlers.ContentAppEndpointManagerImpl;
import com.matter.tv.server.model.ContentApp;
import com.matter.tv.server.receivers.ContentAppDiscoveryService;
import com.matter.tv.server.tvapp.AppPlatform;
import com.matter.tv.server.tvapp.ContentAppSupportedCluster;
import com.matter.tv.server.utils.EndpointsDataStore;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
* This class facilitates the communication with the ContentAppPlatform. It uses the JNI interface
Expand Down Expand Up @@ -168,6 +172,8 @@ public void addContentApp(ContentApp app) {
app.getAppName(),
app.getProductId(),
app.getVersion(),
app.getSupportedClusters(),
mapSupportedCluster(app.getSupportedClusters()),
desiredEndpointId,
new ContentAppEndpointManagerImpl(context));
} else {
Expand All @@ -178,6 +184,7 @@ public void addContentApp(ContentApp app) {
app.getAppName(),
app.getProductId(),
app.getVersion(),
mapSupportedCluster(app.getSupportedClusters()),
new ContentAppEndpointManagerImpl(context));
}
if (retEndpointId > 0) {
Expand All @@ -187,4 +194,16 @@ public void addContentApp(ContentApp app) {
Log.e(TAG, "Could not add content app as endpoint. App Name " + app.getAppName());
}
}

private Collection<ContentAppSupportedCluster> mapSupportedClusters(Collection<SupportedCluster> supportedClusters) {
supportedClusters.stream().map(AppPlatformService::mapSupportedCluster).collect(Collectors.toList());
}

private static ContentAppSupportedCluster mapSupportedCluster(SupportedCluster cluster) {
return new ContentAppSupportedCluster(
cluster.clusterIdentifier,
cluster.features,
cluster.optionalCommandIdentifiers,
cluster.optionalAttributesIdentifiers);
}
}
1 change: 1 addition & 0 deletions examples/tv-app/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ android_library("java") {
"java/src/com/matter/tv/server/tvapp/ChannelProgramResponse.java",
"java/src/com/matter/tv/server/tvapp/Clusters.java",
"java/src/com/matter/tv/server/tvapp/ContentAppEndpointManager.java",
"java/src/com/matter/tv/server/tvapp/ContentAppSupportedCluster.java",
"java/src/com/matter/tv/server/tvapp/ContentLaunchBrandingInformation.java",
"java/src/com/matter/tv/server/tvapp/ContentLaunchEntry.java",
"java/src/com/matter/tv/server/tvapp/ContentLaunchManager.java",
Expand Down
19 changes: 10 additions & 9 deletions examples/tv-app/android/java/AppImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,11 @@ ContentApp * ContentAppFactoryImpl::LoadContentApp(const CatalogVendorApp & vend
}

EndpointId ContentAppFactoryImpl::AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName,
uint16_t productId, const char * szApplicationVersion, jobject manager)
uint16_t productId, const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters,
jobject manager)
{
DataVersion * dataVersionBuf = new DataVersion[ArraySize(contentAppClusters)];
ContentAppImpl * app = new ContentAppImpl(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, "",
ContentAppImpl * app = new ContentAppImpl(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, "", std::move(supportedClusters),
mAttributeDelegate, mCommandDelegate);
EndpointId epId = ContentAppPlatform::GetInstance().AddContentApp(
app, &contentAppEndpoint, Span<DataVersion>(dataVersionBuf, ArraySize(contentAppClusters)),
Expand All @@ -355,11 +356,11 @@ EndpointId ContentAppFactoryImpl::AddContentApp(const char * szVendorName, uint1
}

EndpointId ContentAppFactoryImpl::AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName,
uint16_t productId, const char * szApplicationVersion, jobject manager,
EndpointId desiredEndpointId)
uint16_t productId, const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters,
EndpointId desiredEndpointId, jobject manager)
{
DataVersion * dataVersionBuf = new DataVersion[ArraySize(contentAppClusters)];
ContentAppImpl * app = new ContentAppImpl(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, "",
ContentAppImpl * app = new ContentAppImpl(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, "", std::move(supportedClusters),
mAttributeDelegate, mCommandDelegate);
EndpointId epId = ContentAppPlatform::GetInstance().AddContentApp(
app, &contentAppEndpoint, Span<DataVersion>(dataVersionBuf, ArraySize(contentAppClusters)),
Expand Down Expand Up @@ -480,21 +481,21 @@ CHIP_ERROR InitVideoPlayerPlatform(jobject contentAppEndpointManager)
}

EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, jobject manager)
const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters, jobject manager)
{
#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
ChipLogProgress(DeviceLayer, "AppImpl: AddContentApp vendorId=%d applicationName=%s ", vendorId, szApplicationName);
return gFactory.AddContentApp(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, manager);
return gFactory.AddContentApp(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, std::move(supportedClusters), manager);
#endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
return kInvalidEndpointId;
}

EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, EndpointId endpointId, jobject manager)
const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters, EndpointId endpointId, jobject manager)
{
#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
ChipLogProgress(DeviceLayer, "AppImpl: AddContentApp vendorId=%d applicationName=%s ", vendorId, szApplicationName);
return gFactory.AddContentApp(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, manager, endpointId);
return gFactory.AddContentApp(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, std::move(supportedClusters), endpointId, manager);
#endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
return kInvalidEndpointId;
}
Expand Down
27 changes: 17 additions & 10 deletions examples/tv-app/android/java/AppImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <lib/support/JniReferences.h>
#include <stdbool.h>
#include <stdint.h>
#include <vector>

#include "../include/account-login/AccountLoginManager.h"
#include "../include/application-basic/ApplicationBasicManager.h"
Expand All @@ -56,9 +57,11 @@

CHIP_ERROR InitVideoPlayerPlatform(jobject contentAppEndpointManager);
EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, jobject manager);
const char * szApplicationVersion, std::vector<chip::AppPlatform::ContentApp::SupportedCluster> supportedClusters,
jobject manager);
EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, EndpointId endpointId, jobject manager);
const char * szApplicationVersion, std::vector<chip::AppPlatform::ContentApp::SupportedCluster> supportedClusters,
EndpointId endpointId, jobject manager);
EndpointId RemoveContentApp(EndpointId epId);
void ReportAttributeChange(EndpointId epId, chip::ClusterId clusterId, chip::AttributeId attributeId);

Expand All @@ -81,6 +84,7 @@ using TargetNavigatorDelegate = app::Clusters::TargetNavigator::Delegate;
using SupportedProtocolsBitmap = app::Clusters::ContentLauncher::SupportedProtocolsBitmap;
using ContentAppAttributeDelegate = chip::AppPlatform::ContentAppAttributeDelegate;
using ContentAppCommandDelegate = chip::AppPlatform::ContentAppCommandDelegate;
using SupportedCluster = chip::AppPlatform::ContentApp::SupportedCluster;

static const int kCatalogVendorId = CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID;

Expand All @@ -91,8 +95,9 @@ class DLL_EXPORT ContentAppImpl : public ContentApp
{
public:
ContentAppImpl(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, const char * setupPIN, ContentAppAttributeDelegate * attributeDelegate,
ContentAppCommandDelegate * commandDelegate) :
const char * szApplicationVersion, const char * setupPIN, std::vector<SupportedCluster> supportedClusters,
ContentAppAttributeDelegate * attributeDelegate, ContentAppCommandDelegate * commandDelegate) :
ContentApp{ supportedClusters },
mApplicationBasicDelegate(kCatalogVendorId, BuildAppId(vendorId), szVendorName, vendorId, szApplicationName, productId,
szApplicationVersion),
mAccountLoginDelegate(commandDelegate, setupPIN),
Expand Down Expand Up @@ -160,10 +165,12 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory
ContentApp * LoadContentApp(const CatalogVendorApp & vendorApp) override;

EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, jobject manager);
const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters,
jobject manager);

EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
const char * szApplicationVersion, jobject manager, EndpointId desiredEndpointId);
const char * szApplicationVersion, std::vector<SupportedCluster> supportedClusters,
EndpointId desiredEndpointId, jobject manager);

EndpointId RemoveContentApp(EndpointId epId);

Expand Down Expand Up @@ -193,10 +200,10 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory

protected:
std::vector<ContentAppImpl *> mContentApps{
new ContentAppImpl("Vendor1", 1, "exampleid", 11, "Version1", "20202021", nullptr, nullptr),
new ContentAppImpl("Vendor2", 65521, "exampleString", 32768, "Version2", "20202021", nullptr, nullptr),
new ContentAppImpl("Vendor3", 9050, "App3", 22, "Version3", "20202021", nullptr, nullptr),
new ContentAppImpl("TestSuiteVendor", 1111, "applicationId", 22, "v2", "20202021", nullptr, nullptr)
new ContentAppImpl("Vendor1", 1, "exampleid", 11, "Version1", "20202021", {}, nullptr, nullptr),
new ContentAppImpl("Vendor2", 65521, "exampleString", 32768, "Version2", "20202021", {}, nullptr, nullptr),
new ContentAppImpl("Vendor3", 9050, "App3", 22, "Version3", "20202021", {}, nullptr, nullptr),
new ContentAppImpl("TestSuiteVendor", 1111, "applicationId", 22, "v2", "20202021", {}, nullptr, nullptr)
};
std::vector<DataVersion *> mDataVersions{};

Expand Down
87 changes: 83 additions & 4 deletions examples/tv-app/android/java/AppPlatform-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <lib/support/CHIPJNIError.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <app/app-platform/ContentApp.h>

using namespace chip;
using namespace chip::app;
Expand All @@ -34,6 +35,9 @@ using namespace chip::Credentials;
* com.matter.tv.server.tvapp.AppPlatform class.
*/

// Forward declaration
std::vector<ContentApp::SupportedCluster> convert_to_cpp(JNIEnv* env, jobject supportedClusters);

#define JNI_METHOD(RETURN, METHOD_NAME) \
extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_tv_server_tvapp_AppPlatform_##METHOD_NAME

Expand All @@ -44,7 +48,8 @@ JNI_METHOD(void, nativeInit)(JNIEnv *, jobject app, jobject contentAppEndpointMa
}

JNI_METHOD(jint, addContentApp)
(JNIEnv *, jobject, jstring vendorName, jint vendorId, jstring appName, jint productId, jstring appVersion, jobject manager)
(JNIEnv *, jobject, jstring vendorName, jint vendorId, jstring appName, jint productId, jstring appVersion, jobject supportedClusters,
jobject manager)
{
chip::DeviceLayer::StackLock lock;
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
Expand All @@ -53,12 +58,12 @@ JNI_METHOD(jint, addContentApp)
JniUtfString aName(env, appName);
JniUtfString aVersion(env, appVersion);
EndpointId epId = AddContentApp(vName.c_str(), static_cast<uint16_t>(vendorId), aName.c_str(), static_cast<uint16_t>(productId),
aVersion.c_str(), manager);
aVersion.c_str(), convert_to_cpp(env, supportedClusters), manager);
return static_cast<uint16_t>(epId);
}

JNI_METHOD(jint, addContentAppAtEndpoint)
(JNIEnv *, jobject, jstring vendorName, jint vendorId, jstring appName, jint productId, jstring appVersion, jint endpointId,
(JNIEnv *, jobject, jstring vendorName, jint vendorId, jstring appName, jint productId, jstring appVersion, jobject supportedClusters, jint endpointId,
jobject manager)
{
chip::DeviceLayer::StackLock lock;
Expand All @@ -68,7 +73,7 @@ JNI_METHOD(jint, addContentAppAtEndpoint)
JniUtfString aName(env, appName);
JniUtfString aVersion(env, appVersion);
EndpointId epId = AddContentApp(vName.c_str(), static_cast<uint16_t>(vendorId), aName.c_str(), static_cast<uint16_t>(productId),
aVersion.c_str(), static_cast<EndpointId>(endpointId), manager);
aVersion.c_str(), convert_to_cpp(env, supportedClusters), static_cast<EndpointId>(endpointId), manager);
return static_cast<uint16_t>(epId);
}

Expand All @@ -93,3 +98,77 @@ JNI_METHOD(void, addSelfVendorAsAdmin)
{
AddSelfVendorAsAdmin();
}

std::vector<uint32_t> consume_and_convert_to_cpp(JNIEnv* env, jobject intArrayObject) {
std::vector<uint32_t> uintVector;
if (intArrayObject != nullptr) {
jsize length = env->GetArrayLength(static_cast<jintArray>(intArrayObject));
jint* elements = env->GetIntArrayElements(static_cast<jintArray>(intArrayObject), nullptr);
if (elements != nullptr) {
// OBS: Implicit type ambiguation from int32_t to uint32_t
uintVector.assign(elements, elements + length);
env->ReleaseIntArrayElements(static_cast<jintArray>(intArrayObject), elements, JNI_ABORT);
}
env->DeleteLocalRef(intArrayObject);
}
return uintVector;
}

std::vector<ContentApp::SupportedCluster> convert_to_cpp(JNIEnv* env, jobject supportedClustersObject) {
if (supportedClustersObject == nullptr || env == nullptr) {
return {};
}

// Find Java classes. WARNING: Reflection
jclass collectionClass = env->FindClass("java/util/Collection");
jclass iteratorClass = env->FindClass("java/util/Iterator");
jclass clusterClass = env->FindClass("com/matter/tv/server/tvapp/SupportedCluster");
if (collectionClass == nullptr || iteratorClass == nullptr || clusterClass == nullptr) {
return {};
}

// Find Java methods. WARNING: Reflection
jmethodID iteratorMethod = env->GetMethodID(collectionClass, "iterator", "()Ljava/util/Iterator;");
jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z");
jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
if (iteratorMethod == nullptr || hasNextMethod == nullptr || nextMethod == nullptr) {
return {};
}

// Find Java SupportedCluster fields. WARNING: Reflection
jfieldID clusterIdentifierField = env->GetFieldID(clusterClass, "clusterIdentifier", "I");
jfieldID featuresField = env->GetFieldID(clusterClass, "features", "I");
jfieldID optionalCommandIdentifiersField = env->GetFieldID(clusterClass, "optionalCommandIdentifiers", "[I");
jfieldID optionalAttributesIdentifiersField = env->GetFieldID(clusterClass, "optionalAttributesIdentifiers", "[I");
if (clusterIdentifierField == nullptr || featuresField == nullptr || optionalCommandIdentifiersField == nullptr || optionalAttributesIdentifiersField == nullptr) {
return {};
}

// Find Set Iterator Object
jobject iteratorObject = env->CallObjectMethod(supportedClustersObject, iteratorMethod);
if (iteratorObject == nullptr) {
return {};
}

// Iterate over the Java Collection and convert each SupportedCluster
std::vector<SupportedCluster> supportedClusters;
while (env->CallBooleanMethod(iteratorObject, hasNextMethod)) {
jobject clusterObject = env->CallObjectMethod(iteratorObject, nextMethod);
if (clusterObject != nullptr) {
jint clusterIdentifier = env->GetIntField(clusterObject, clusterIdentifierField);
jint features = env->GetIntField(clusterObject, featuresField);
jobject commandIdsObject = env->GetObjectField(clusterObject, optionalCommandIdentifiersField);
jobject attributeIdsObject = env->GetObjectField(clusterObject, optionalAttributesIdentifiersField);
// OBS: Implicit type ambiguation from int32_t to uint32_t
supportedClusters.emplace_back(
clusterIdentifier,
features,
consume_and_convert_to_cpp(env, commandIdsObject),
consume_and_convert_to_cpp(env, attributeIdsObject));
env->DeleteLocalRef(clusterObject);
}
}
env->DeleteLocalRef(iteratorObject);

return supportedClusters;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package com.matter.tv.server.tvapp;

import java.util.Collection;

/*
* This class is provides the JNI interface to the linux layer of the ContentAppPlatform
*/
Expand All @@ -37,6 +39,7 @@ public native int addContentApp(
String appName,
int productId,
String appVersion,
Collection<ContentAppSupportedCluster> supportedClusters,
ContentAppEndpointManager manager);

// Method to add a content app at an existing endpoint after restart of the matter server
Expand All @@ -46,6 +49,7 @@ public native int addContentAppAtEndpoint(
String appName,
int productId,
String appVersion,
Collection<ContentAppSupportedCluster> supportedClusters,
int endpointId,
ContentAppEndpointManager manager);

Expand Down
Loading

0 comments on commit 1084960

Please sign in to comment.