diff --git a/examples/tv-app/android/App/.idea/deploymentTargetDropDown.xml b/examples/tv-app/android/App/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 00000000000000..2bb42d2582175f
--- /dev/null
+++ b/examples/tv-app/android/App/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml b/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml
index c67adc438f879a..9ba74639750d1d 100644
--- a/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml
+++ b/examples/tv-app/android/App/content-app/src/main/AndroidManifest.xml
@@ -16,7 +16,10 @@
android:supportsRtl="true"
android:enabled="true"
android:theme="@style/Theme.ContentApp">
-
+
+
+
+
diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java
index 0f01b9ee71f394..a5c6a2c9d9807e 100644
--- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java
+++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java
@@ -28,6 +28,8 @@ public void onReceive(Context context, Intent intent) {
new StringBuilder()
.append("Received matter command: ")
.append(intent.getAction())
+ .append(". Payload : ")
+ .append(new String(commandPayload))
.toString());
PendingIntent pendingIntent =
diff --git a/examples/tv-app/android/App/content-app/src/main/res/raw/static_matter_clusters b/examples/tv-app/android/App/content-app/src/main/res/raw/static_matter_clusters
index 836bfe530df37a..626a145b863f7a 100644
--- a/examples/tv-app/android/App/content-app/src/main/res/raw/static_matter_clusters
+++ b/examples/tv-app/android/App/content-app/src/main/res/raw/static_matter_clusters
@@ -1,13 +1,16 @@
{
"clusters": [
{
- "identifier": "0x050a",
- "features": "CS"
+ "identifier": 1289,
+ "features": ["NV", "LK", "NK"]
},
{
- "identifier": "0x0506",
- "features": "AS",
- "optionalCommands" : [4, 5]
+ "identifier": 1290,
+ "features": ["CS"],
+ },
+ {
+ "identifier": 1286,
+ "features": ["AS"],
}
]
}
\ No newline at end of file
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java
index 05824a77bce180..fbeed33502a439 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/MainActivity.java
@@ -7,21 +7,12 @@
import com.matter.tv.server.fragments.ContentAppFragment;
import com.matter.tv.server.fragments.QrCodeFragment;
import com.matter.tv.server.fragments.TerminalFragment;
-import com.matter.tv.server.receivers.ContentAppDiscoveryService;
import java.util.LinkedHashMap;
public class MainActivity extends AppCompatActivity {
private LinkedHashMap packages = new LinkedHashMap<>();
- @Override
- protected void onRestart() {
- super.onRestart();
- packages.clear();
- ContentAppDiscoveryService.getReceiverInstance()
- .initializeMatterApps(this.getApplicationContext());
- }
-
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
item -> {
Fragment selectedFragment = null;
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java
index 4372c550f3f49e..89b8804af3677d 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/ContentAppFragment.java
@@ -17,11 +17,10 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.matter.tv.server.R;
+import com.matter.tv.server.model.ContentApp;
import com.matter.tv.server.receivers.ContentAppDiscoveryService;
-import com.matter.tv.server.service.ContentAppAgentService;
+import com.matter.tv.server.service.MatterServant;
import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
/**
* A simple {@link Fragment} subclass. Use the {@link ContentAppFragment#newInstance} factory method
@@ -33,8 +32,6 @@ public class ContentAppFragment extends Fragment {
private BroadcastReceiver broadcastReceiver;
private ListView pkgUpdatesView;
- private LinkedHashMap packages = new LinkedHashMap<>();
-
public ContentAppFragment() {
// Required empty public constructor
}
@@ -65,8 +62,10 @@ public void onCreate(Bundle savedInstanceState) {
public void onResume() {
super.onResume();
- ArrayList> lst = new ArrayList<>(packages.entrySet());
-
+ ContentAppDiscoveryService.getReceiverInstance().registerSelf(getContext());
+ ArrayList lst =
+ new ArrayList(
+ ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().keySet());
ContentAppListAdapter adapter =
new ContentAppListAdapter(getContext(), R.layout.applist_item, lst);
@@ -82,14 +81,12 @@ public View onCreateView(
return inflater.inflate(R.layout.fragment_content_app, container, false);
}
- private class ContentAppListAdapter extends ArrayAdapter> {
+ private class ContentAppListAdapter extends ArrayAdapter {
private int layout;
public ContentAppListAdapter(
- @NonNull Context context,
- int resource,
- @NonNull ArrayList> packages) {
+ @NonNull Context context, int resource, @NonNull ArrayList packages) {
super(context, resource, packages);
layout = resource;
}
@@ -104,22 +101,25 @@ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup
ViewHolder viewHolder = new ViewHolder();
viewHolder.appName = convertView.findViewById(R.id.appNameTextView);
- viewHolder.appDetails = convertView.findViewById(R.id.appDetailsTextView);
- viewHolder.appName.setText(getItem(position).getKey());
- viewHolder.appDetails.setText(getItem(position).getValue());
+ viewHolder.appName.setText(getItem(position));
viewHolder.sendMessageButton = convertView.findViewById(R.id.sendMessageButton);
viewHolder.sendMessageButton.setText(R.string.send_command);
viewHolder.sendMessageButton.setOnClickListener(
view -> {
Log.i(TAG, "Button was clicked for " + position);
- ContentAppAgentService.sendCommand(
- getActivity().getApplicationContext(), getItem(position).getKey());
+ for (ContentApp app :
+ ContentAppDiscoveryService.getReceiverInstance()
+ .getDiscoveredContentApps()
+ .values()) {
+ if (app.getAppName().equals(getItem(position))) {
+ MatterServant.get().sendTestMessage(app.getEndpointId(), "My Native Message");
+ }
+ }
});
convertView.setTag(viewHolder);
} else {
mainViewHolder = (ViewHolder) convertView.getTag();
- mainViewHolder.appName.setText(getItem(position).getKey());
- mainViewHolder.appDetails.setText(getItem(position).getValue());
+ mainViewHolder.appName.setText(getItem(position));
}
return convertView;
}
@@ -132,18 +132,14 @@ private void registerReceiver(ArrayAdapter adapter) {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String packageName = intent.getStringExtra("com.matter.tv.server.appagent.add.pkg");
- if (action.equals("com.matter.tv.server.appagent.add")) {
- packages.put(
- packageName, intent.getStringExtra("com.matter.tv.server.appagent.add.clusters"));
+ if (action.equals("com.matter.tv.server.appagent.add")
+ || action.equals("com.matter.tv.server.appagent.remove")) {
adapter.clear();
- adapter.addAll(packages.entrySet().toArray());
+ adapter.addAll(
+ ContentAppDiscoveryService.getReceiverInstance()
+ .getDiscoveredContentApps()
+ .entrySet());
adapter.notifyDataSetChanged();
- } else if (action.equals("com.matter.tv.server.appagent.remove")) {
- if (packages.remove(packageName) != null) {
- adapter.clear();
- adapter.addAll(packages.entrySet().toArray());
- adapter.notifyDataSetChanged();
- }
}
}
};
@@ -152,14 +148,10 @@ public void onReceive(Context context, Intent intent) {
getContext()
.registerReceiver(
broadcastReceiver, new IntentFilter("com.matter.tv.server.appagent.remove"));
-
- ContentAppDiscoveryService.getReceiverInstance().registerSelf(getContext());
- ContentAppDiscoveryService.getReceiverInstance().initializeMatterApps(getContext());
}
public class ViewHolder {
TextView appName;
- TextView appDetails;
Button sendMessageButton;
}
}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ContentAppEndpointManagerImpl.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ContentAppEndpointManagerImpl.java
new file mode 100644
index 00000000000000..035e7cd84e8c44
--- /dev/null
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/handlers/ContentAppEndpointManagerImpl.java
@@ -0,0 +1,31 @@
+package com.matter.tv.server.handlers;
+
+import android.content.Context;
+import android.util.Log;
+import com.matter.tv.server.model.ContentApp;
+import com.matter.tv.server.receivers.ContentAppDiscoveryService;
+import com.matter.tv.server.service.ContentAppAgentService;
+import com.tcl.chip.tvapp.ContentAppEndpointManager;
+
+public class ContentAppEndpointManagerImpl implements ContentAppEndpointManager {
+
+ private static final String TAG = "MatterMainActivity";
+ private final Context context;
+
+ public ContentAppEndpointManagerImpl(Context context) {
+ this.context = context;
+ }
+
+ public String sendCommand(int endpointId, String commandPayload) {
+ Log.d(TAG, "Received a command for endpointId " + endpointId + ". Message " + commandPayload);
+ for (ContentApp app :
+ ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values()) {
+ if (app.getEndpointId() == endpointId) {
+ Log.d(
+ TAG, "Sending a command for endpointId " + endpointId + ". Message " + commandPayload);
+ ContentAppAgentService.sendCommand(context, app.getAppName(), commandPayload);
+ }
+ }
+ return "Success";
+ }
+}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/model/ContentApp.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/model/ContentApp.java
new file mode 100644
index 00000000000000..5d021c198f5830
--- /dev/null
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/model/ContentApp.java
@@ -0,0 +1,56 @@
+package com.matter.tv.server.model;
+
+import com.matter.tv.app.api.SupportedCluster;
+import java.util.Collections;
+import java.util.Set;
+
+public class ContentApp {
+
+ private String appName;
+ private String vendorName;
+ private int vendorId;
+ private int productId;
+ private Set supportedClusters;
+ private int endpoint;
+
+ public ContentApp(
+ String appName,
+ String vendorName,
+ int vendorId,
+ int productId,
+ Set supportedClusters) {
+ this.vendorName = vendorName;
+ this.appName = appName;
+ this.vendorId = vendorId;
+ this.productId = productId;
+ this.supportedClusters = supportedClusters;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public String getVendorName() {
+ return vendorName;
+ }
+
+ public int getVendorId() {
+ return vendorId;
+ }
+
+ public int getProductId() {
+ return productId;
+ }
+
+ public int getEndpointId() {
+ return endpoint;
+ }
+
+ public void setEndpointId(int endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public Set getSupportedClusters() {
+ return Collections.unmodifiableSet(supportedClusters);
+ }
+}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java
index b5b3793d0b414b..0c4555db2b86e0 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/receivers/ContentAppDiscoveryService.java
@@ -11,26 +11,39 @@
import android.os.Bundle;
import android.util.Log;
import com.matter.tv.app.api.MatterIntentConstants;
+import com.matter.tv.app.api.SupportedCluster;
+import com.matter.tv.server.model.ContentApp;
import com.matter.tv.server.utils.ResourceUtils;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class ContentAppDiscoveryService extends BroadcastReceiver {
private static final String TAG = "ContentAppDiscoveryService";
- static final String CLUSTERS_RESOURCE_METADATA_KEY = "com.matter.app_agent_api.clusters";
+ static final String CLUSTERS_RESOURCE_METADATA_KEY = "com.matter.tv.app.api.clusters";
+ static final String MATTER_VENDOR_NAME_METADATA_KEY = "com.matter.tv.app.api.vendor_name";
+ static final String MATTER_VENDOR_ID_METADATA_KEY = "com.matter.tv.app.api.vendor_id";
+ static final String MATTER_PRODUCT_ID_METADATA_KEY = "com.matter.tv.app.api.product_id";
+
private static final String ANDROID_PACKAGE_REMOVED_ACTION =
"android.intent.action.PACKAGE_REMOVED";
private static final String ANDROID_PACKAGE_ADDED_ACTION = "android.intent.action.PACKAGE_ADDED";
+ private static final String ANDROID_PACKAGE_REPLACED_ACTION =
+ "android.intent.action.PACKAGE_REPLACED";
private static ResourceUtils resourceUtils = ResourceUtils.getInstance();
private static final ContentAppDiscoveryService instance = new ContentAppDiscoveryService();
- public ContentAppDiscoveryService() {}
+ private ContentAppDiscoveryService() {}
private volatile boolean registered = false;
+ private Map applications = new HashMap<>();
+
@Override
public void onReceive(Context context, Intent intent) {
final String intentAction = intent.getAction();
@@ -41,10 +54,8 @@ public void onReceive(Context context, Intent intent) {
}
switch (intentAction) {
- case Intent.ACTION_BOOT_COMPLETED:
- // discoveryAgent.init();
- break;
case ANDROID_PACKAGE_ADDED_ACTION:
+ case ANDROID_PACKAGE_REPLACED_ACTION:
handlePackageAdded(intent, context);
break;
case ANDROID_PACKAGE_REMOVED_ACTION:
@@ -62,8 +73,6 @@ public void onReceive(Context context, Intent intent) {
private void handlePackageAdded(final Intent intent, final Context context) {
String pkg = intent.getData().getSchemeSpecificPart();
- Log.i(TAG, pkg + " Added. MATTERSERVER");
-
handlePackageAdded(context, pkg);
}
@@ -72,24 +81,35 @@ private void handlePackageAdded(Context context, String pkg) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(pkg, PackageManager.GET_META_DATA);
if (appInfo.metaData == null) {
- Log.i(TAG, pkg + " has no metadata.");
return;
}
int resId = appInfo.metaData.getInt(CLUSTERS_RESOURCE_METADATA_KEY, 0);
+ int vendorId = appInfo.metaData.getInt(MATTER_VENDOR_ID_METADATA_KEY, -1);
+ int productId = appInfo.metaData.getInt(MATTER_PRODUCT_ID_METADATA_KEY, -1);
+ String vendorName = appInfo.metaData.getString(MATTER_VENDOR_NAME_METADATA_KEY, "");
+
+ if (vendorId == -1 || productId == -1) {
+ return;
+ }
+
+ Set supportedClusters;
Log.d(TAG, "got static capability for package " + pkg + ", resourceId: " + resId);
if (resId != 0) {
Resources res = pm.getResourcesForApplication(appInfo);
- String rawJson = resourceUtils.getRawTextResource(res, resId);
- Log.d(TAG, "Got capabilities resource:\n" + rawJson);
-
- Intent in = new Intent("com.matter.tv.server.appagent.add");
- Bundle extras = new Bundle();
- extras.putString("com.matter.tv.server.appagent.add.pkg", pkg);
- extras.putString("com.matter.tv.server.appagent.add.clusters", rawJson);
- in.putExtras(extras);
- context.sendBroadcast(in);
+ supportedClusters = resourceUtils.getSupportedClusters(res, resId);
+ } else {
+ supportedClusters = new HashSet<>();
}
+
+ ContentApp app = new ContentApp(pkg, vendorName, vendorId, productId, supportedClusters);
+ applications.put(pkg, app);
+
+ Intent in = new Intent("com.matter.tv.server.appagent.add");
+ Bundle extras = new Bundle();
+ extras.putString("com.matter.tv.server.appagent.add.pkg", pkg);
+ in.putExtras(extras);
+ context.sendBroadcast(in);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not find package " + pkg, e);
}
@@ -97,7 +117,10 @@ private void handlePackageAdded(Context context, String pkg) {
private void handlePackageRemoved(final Intent intent, final Context context) {
String pkg = intent.getData().getSchemeSpecificPart();
- Log.i(TAG, pkg + " Removed. MATTERSERVER");
+ Log.i(TAG, pkg + " Removed.");
+
+ applications.remove(pkg);
+
Intent in = new Intent("com.matter.tv.server.appagent.remove");
Bundle extras = new Bundle();
extras.putString("com.matter.tv.server.appagent.add.pkg", pkg);
@@ -106,25 +129,26 @@ private void handlePackageRemoved(final Intent intent, final Context context) {
}
public void registerSelf(Context context) {
- Log.i(TAG, "Starting the registration of the matter package update receiver");
if (registered) {
Log.i(TAG, "Package update receiver for matter already registered");
return;
} else {
registered = true;
}
+ registerPackageAction(context, ANDROID_PACKAGE_ADDED_ACTION);
+ registerPackageAction(context, ANDROID_PACKAGE_REMOVED_ACTION);
+ registerPackageAction(context, ANDROID_PACKAGE_REPLACED_ACTION);
+ initializeMatterApps(context);
+ Log.i(TAG, "Registered the matter package updates receiver");
+ }
- Log.i(TAG, "Trying to register the matter package update receiver");
- IntentFilter pckAdded = new IntentFilter(ANDROID_PACKAGE_ADDED_ACTION);
- pckAdded.addDataScheme("package");
- context.registerReceiver(this, pckAdded);
- IntentFilter pckRemoved = new IntentFilter(ANDROID_PACKAGE_REMOVED_ACTION);
- pckRemoved.addDataScheme("package");
- context.registerReceiver(this, pckRemoved);
- Log.i(TAG, "Registered the matter package update receiver");
+ private void registerPackageAction(final Context context, final String action) {
+ IntentFilter intent = new IntentFilter(action);
+ intent.addDataScheme("package");
+ context.registerReceiver(this, intent);
}
- public void initializeMatterApps(Context context) {
+ private void initializeMatterApps(Context context) {
Set matterApps = getMatterApps(context);
for (String matterApp : matterApps) {
handlePackageAdded(context, matterApp);
@@ -156,4 +180,12 @@ private Set getMatterApps(Context context) {
public static ContentAppDiscoveryService getReceiverInstance() {
return instance;
}
+
+ public Map getDiscoveredContentApps() {
+ return Collections.unmodifiableMap(applications);
+ }
+
+ public ContentApp getDiscoveredContentApp(String appName) {
+ return applications.get(appName);
+ }
}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java
index 40e997a692e211..b1799990f99575 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ContentAppAgentService.java
@@ -51,10 +51,10 @@ public IBinder onBind(final Intent intent) {
return null;
}
- public static void sendCommand(Context context, String packageName) {
+ public static void sendCommand(Context context, String packageName, String payload) {
Intent in = new Intent(MatterIntentConstants.ACTION_MATTER_COMMAND);
Bundle extras = new Bundle();
- extras.putByteArray(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD, "test payload".getBytes());
+ extras.putByteArray(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD, payload.getBytes());
in.putExtras(extras);
in.setPackage(packageName);
int flags = Intent.FLAG_INCLUDE_STOPPED_PACKAGES;
@@ -84,7 +84,7 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
Log.d(
TAG,
"Command response "
- + intent.getByteArrayExtra(MatterIntentConstants.EXTRA_RESPONSE_PAYLOAD));
+ + new String(intent.getByteArrayExtra(MatterIntentConstants.EXTRA_RESPONSE_PAYLOAD)));
// Send the response back to the client.
}
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 15e8ee5c4cdd9c..574aadb0313ca4 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
@@ -28,6 +28,8 @@
import chip.platform.NsdManagerServiceResolver;
import chip.platform.PreferencesConfigurationManager;
import chip.platform.PreferencesKeyValueStoreManager;
+import com.matter.tv.server.handlers.ContentAppEndpointManagerImpl;
+import com.matter.tv.server.model.ContentApp;
import com.tcl.chip.tvapp.ChannelManagerStub;
import com.tcl.chip.tvapp.Clusters;
import com.tcl.chip.tvapp.ContentLaunchManagerStub;
@@ -62,7 +64,12 @@ public static MatterServant get() {
return SingletonHolder.instance;
}
+ private Context context;
+
public void init(@NonNull Context context) {
+
+ this.context = context;
+
// The order is important, must
// first new TvApp to load dynamic library
// then chipPlatform to prepare platform
@@ -145,4 +152,18 @@ public void sendCustomCommand(String customCommand) {
public void updateLevel(int value) {
mTvApp.setCurrentLevel(mLevelEndpoint, value);
}
+
+ public int addContentApp(ContentApp app) {
+ return mTvApp.addContentApp(
+ app.getVendorName(),
+ app.getVendorId(),
+ app.getAppName(),
+ app.getProductId(),
+ "1.0",
+ new ContentAppEndpointManagerImpl(context));
+ }
+
+ public void sendTestMessage(int endpoint, String message) {
+ mTvApp.sendTestMessage(endpoint, message);
+ }
}
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServantService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServantService.java
index 9efc03f29d2a81..cc68f49c09be07 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServantService.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/MatterServantService.java
@@ -12,6 +12,8 @@
import androidx.core.app.NotificationCompat;
import com.matter.tv.server.MainActivity;
import com.matter.tv.server.R;
+import com.matter.tv.server.model.ContentApp;
+import com.matter.tv.server.receivers.ContentAppDiscoveryService;
public class MatterServantService extends Service {
private static final String CHANNEL_ID = "Matter";
@@ -19,7 +21,15 @@ public class MatterServantService extends Service {
@Override
public void onCreate() {
super.onCreate();
+ // Start Matter Server
MatterServant.get().init(this.getApplicationContext());
+
+ // Register for packages updates
+ ContentAppDiscoveryService.getReceiverInstance().registerSelf(this.getApplicationContext());
+ for (ContentApp app :
+ ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values()) {
+ app.setEndpointId(MatterServant.get().addContentApp(app));
+ }
}
@Nullable
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java
index 769b039efbd267..71ccc02f1799b2 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/ResourceUtils.java
@@ -1,15 +1,24 @@
package com.matter.tv.server.utils;
import android.content.res.Resources;
+import android.util.JsonReader;
import android.util.Log;
-import java.io.BufferedReader;
+import com.matter.tv.app.api.SupportedCluster;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/** Util class for dealing with android Resources. */
public class ResourceUtils {
private static final String TAG = "ResourceUtils";
private static final ResourceUtils resourceUtils = new ResourceUtils();
+ private static final String KEY_CLUSTERS = "clusters";
+ private static final String KEY_CLUSTER_ID = "identifier";
+ private static final String KEY_FEATURES = "features";
+ private static final String KEY_OPTIONAL_COMMANDS = "optionalCommands";
private ResourceUtils() {}
@@ -17,35 +26,76 @@ public static ResourceUtils getInstance() {
return resourceUtils;
}
+ /**
+ * { "clusters": [ { "identifier": 1234, "features": ["CS"] }, { "identifier": 1235, "features":
+ * ["AS"], "optionalCommands" : [4, 5] } ] }
+ */
+
/**
* Attempts to open the resource given by the resourceId as a raw resource and return its contents
* as a string.
*
* @param resources Resources object containing the resource
* @param resId resourceId
- * @return String containing the contents of the resource file. Null if an error occurred.
+ * @return Set containing the clusters defined in the resource file. Empty set
+ * if there is a parsing error.
*/
- public String getRawTextResource(final Resources resources, final int resId) {
- // TODO: Enforce some size limit on raw resource text? We don't want to
- // potentially take a long time reading an image file or something like that.
+ public Set getSupportedClusters(final Resources resources, final int resId) {
+ Set supportedClusters = new HashSet<>();
// openRawResource cannot return null, it either succeeds or throws an exception
- try (BufferedReader reader =
- new BufferedReader(new InputStreamReader(resources.openRawResource(resId)))) {
+ try (JsonReader reader =
+ new JsonReader(new InputStreamReader(resources.openRawResource(resId)))) {
- String line;
- StringBuilder result = new StringBuilder();
+ reader.beginObject();
- while ((line = reader.readLine()) != null) {
- result.append(line).append('\n');
+ if (reader.hasNext() && reader.nextName().equals(KEY_CLUSTERS)) {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginObject();
+ SupportedCluster cluster = new SupportedCluster();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (name.equals(KEY_CLUSTER_ID)) {
+ cluster.clusterIdentifier = reader.nextInt();
+ } else if (name.equals(KEY_FEATURES)) {
+ List features = new ArrayList<>();
+ reader.beginArray();
+ while (reader.hasNext()) {
+ features.add(reader.nextString());
+ }
+ reader.endArray();
+ cluster.features = features.toArray(new String[features.size()]);
+ } else if (name.equals(KEY_OPTIONAL_COMMANDS)) {
+ List commands = new ArrayList<>();
+ reader.beginArray();
+ while (reader.hasNext()) {
+ commands.add(reader.nextInt());
+ }
+ reader.endArray();
+ int[] commandIds = new int[commands.size()];
+ int i = 0;
+ for (Integer command : commands) {
+ commandIds[i++] = command;
+ }
+ cluster.optionalCommandIdentifiers = commandIds;
+ } else {
+ reader.skipValue();
+ }
+ }
+ supportedClusters.add(cluster);
+ reader.endObject();
+ }
+ reader.endArray();
+ } else {
+ Log.e(TAG, "Invalid resource (Key:'clusters' not found) for id " + resId);
}
- return result.toString();
+ reader.endObject();
} catch (IOException e) {
Log.e(TAG, "Error reading raw resource for id " + resId);
} catch (Resources.NotFoundException e) {
Log.e(TAG, "Could not find raw resource for id " + resId);
}
-
- return null;
+ return supportedClusters;
}
}
diff --git a/examples/tv-app/android/App/platform-app/src/main/res/layout/applist_item.xml b/examples/tv-app/android/App/platform-app/src/main/res/layout/applist_item.xml
index 6d3dd40d2c2400..678d76814268a5 100644
--- a/examples/tv-app/android/App/platform-app/src/main/res/layout/applist_item.xml
+++ b/examples/tv-app/android/App/platform-app/src/main/res/layout/applist_item.xml
@@ -9,17 +9,10 @@
android:layout_height="wrap_content"
android:text="TextView" />
-
-
+ android:layout_toEndOf="@id/appNameTextView" />
\ No newline at end of file
diff --git a/examples/tv-app/android/BUILD.gn b/examples/tv-app/android/BUILD.gn
index d354573bbd622b..5267c38cb0f217 100644
--- a/examples/tv-app/android/BUILD.gn
+++ b/examples/tv-app/android/BUILD.gn
@@ -44,6 +44,8 @@ shared_library("jni") {
"java/ChannelManager.cpp",
"java/ChannelManager.h",
"java/ClusterChangeAttribute.cpp",
+ "java/ContentAppCommandDelegate.cpp",
+ "java/ContentAppCommandDelegate.h",
"java/ContentLauncherManager.cpp",
"java/ContentLauncherManager.h",
"java/JNIDACProvider.cpp",
@@ -99,6 +101,8 @@ android_library("java") {
"java/src/com/tcl/chip/tvapp/ChannelManager.java",
"java/src/com/tcl/chip/tvapp/ChannelManagerStub.java",
"java/src/com/tcl/chip/tvapp/Clusters.java",
+ "java/src/com/tcl/chip/tvapp/ContentAppEndpointManager.java",
+ "java/src/com/tcl/chip/tvapp/ContentAppEndpointManagerStub.java",
"java/src/com/tcl/chip/tvapp/ContentLaunchBrandingInformation.java",
"java/src/com/tcl/chip/tvapp/ContentLaunchManager.java",
"java/src/com/tcl/chip/tvapp/ContentLaunchManagerStub.java",
diff --git a/examples/tv-app/android/include/cluster-init.cpp b/examples/tv-app/android/include/cluster-init.cpp
index 87eff6e1928a8a..3c83bef42afd9c 100644
--- a/examples/tv-app/android/include/cluster-init.cpp
+++ b/examples/tv-app/android/include/cluster-init.cpp
@@ -49,7 +49,7 @@ static TargetNavigatorManager targetNavigatorManager;
*/
void emberAfApplicationBasicClusterInitCallback(chip::EndpointId endpoint)
{
- ChipLogProgress(Zcl, "TV Linux App: ApplicationBasic::SetDefaultDelegate");
+ ChipLogProgress(Zcl, "TV Android App: ApplicationBasic::SetDefaultDelegate");
chip::app::Clusters::ApplicationBasic::SetDefaultDelegate(endpoint, &applicationBasicManager);
}
@@ -64,7 +64,7 @@ void emberAfApplicationBasicClusterInitCallback(chip::EndpointId endpoint)
*/
void emberAfApplicationLauncherClusterInitCallback(EndpointId endpoint)
{
- ChipLogProgress(Zcl, "TV Linux App: ApplicationLauncher::SetDefaultDelegate");
+ ChipLogProgress(Zcl, "TV Android App: ApplicationLauncher::SetDefaultDelegate");
chip::app::Clusters::ApplicationLauncher::SetDefaultDelegate(endpoint, &applicationLauncherManager);
}
@@ -79,7 +79,7 @@ void emberAfApplicationLauncherClusterInitCallback(EndpointId endpoint)
*/
void emberAfAudioOutputClusterInitCallback(EndpointId endpoint)
{
- ChipLogProgress(Zcl, "TV Linux App: AudioOutput::SetDefaultDelegate");
+ ChipLogProgress(Zcl, "TV Android App: AudioOutput::SetDefaultDelegate");
chip::app::Clusters::AudioOutput::SetDefaultDelegate(endpoint, &audioOutputManager);
}
@@ -94,6 +94,6 @@ void emberAfAudioOutputClusterInitCallback(EndpointId endpoint)
*/
void emberAfTargetNavigatorClusterInitCallback(EndpointId endpoint)
{
- ChipLogProgress(Zcl, "TV Linux App: TargetNavigator::SetDefaultDelegate");
+ ChipLogProgress(Zcl, "TV Android App: TargetNavigator::SetDefaultDelegate");
chip::app::Clusters::TargetNavigator::SetDefaultDelegate(endpoint, &targetNavigatorManager);
}
diff --git a/examples/tv-app/android/include/content-launcher/AppContentLauncherManager.cpp b/examples/tv-app/android/include/content-launcher/AppContentLauncherManager.cpp
index e6563180ead3c4..1d15233ab165f1 100644
--- a/examples/tv-app/android/include/content-launcher/AppContentLauncherManager.cpp
+++ b/examples/tv-app/android/include/content-launcher/AppContentLauncherManager.cpp
@@ -17,14 +17,18 @@
*/
#include "AppContentLauncherManager.h"
+#include "../../java/ContentAppCommandDelegate.h"
using namespace std;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::DataModel;
using namespace chip::app::Clusters::ContentLauncher;
+using ContentAppCommandDelegate = chip::AppPlatform::ContentAppCommandDelegate;
-AppContentLauncherManager::AppContentLauncherManager(list acceptHeaderList, uint32_t supportedStreamingProtocols)
+AppContentLauncherManager::AppContentLauncherManager(ContentAppCommandDelegate commandDelegate, list acceptHeaderList,
+ uint32_t supportedStreamingProtocols) :
+ mCommandDelegate(commandDelegate)
{
mAcceptHeaderList = acceptHeaderList;
mSupportedStreamingProtocols = supportedStreamingProtocols;
@@ -69,8 +73,11 @@ void AppContentLauncherManager::HandleLaunchUrl(CommandResponseHelper
using chip::CharSpan;
using chip::EndpointId;
using chip::app::AttributeValueEncoder;
using chip::app::CommandResponseHelper;
-using ContentLauncherDelegate = chip::app::Clusters::ContentLauncher::Delegate;
-using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LaunchResponse::Type;
-using ParameterType = chip::app::Clusters::ContentLauncher::Structs::Parameter::DecodableType;
-using BrandingInformationType = chip::app::Clusters::ContentLauncher::Structs::BrandingInformation::Type;
+using ContentLauncherDelegate = chip::app::Clusters::ContentLauncher::Delegate;
+using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LaunchResponse::Type;
+using ParameterType = chip::app::Clusters::ContentLauncher::Structs::Parameter::DecodableType;
+using BrandingInformationType = chip::app::Clusters::ContentLauncher::Structs::BrandingInformation::Type;
+using ContentAppCommandDelegate = chip::AppPlatform::ContentAppCommandDelegate;
class AppContentLauncherManager : public ContentLauncherDelegate
{
public:
- AppContentLauncherManager() : AppContentLauncherManager({ "example", "example" }, 0){};
- AppContentLauncherManager(std::list acceptHeaderList, uint32_t supportedStreamingProtocols);
+ AppContentLauncherManager(ContentAppCommandDelegate commandDelegate, std::list acceptHeaderList,
+ uint32_t supportedStreamingProtocols);
void HandleLaunchContent(CommandResponseHelper & helper,
const chip::app::DataModel::DecodableList & parameterList, bool autoplay,
@@ -43,10 +45,13 @@ class AppContentLauncherManager : public ContentLauncherDelegate
CHIP_ERROR HandleGetAcceptHeaderList(AttributeValueEncoder & aEncoder) override;
uint32_t HandleGetSupportedStreamingProtocols() override;
+ void SetEndpointId(EndpointId epId) { mEndpointId = epId; };
+
protected:
std::list mAcceptHeaderList;
uint32_t mSupportedStreamingProtocols;
private:
EndpointId mEndpointId;
+ ContentAppCommandDelegate mCommandDelegate;
};
diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp
index d4ddb18c049a8d..e1bed97c35296c 100644
--- a/examples/tv-app/android/java/AppImpl.cpp
+++ b/examples/tv-app/android/java/AppImpl.cpp
@@ -31,6 +31,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -377,9 +378,9 @@ ContentApp * ContentAppFactoryImpl::LoadContentApp(const CatalogVendorApp & vend
ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl: LoadContentAppByAppId catalogVendorId=%d applicationId=%s ",
vendorApp.catalogVendorId, vendorApp.applicationId);
- for (size_t i = 0; i < ArraySize(mContentApps); ++i)
+ for (size_t i = 0; i < mContentApps.size(); ++i)
{
- auto & app = mContentApps[i];
+ auto & app = mContentApps.at(i);
ChipLogProgress(DeviceLayer, " Looking next=%s ", app.GetApplicationBasicDelegate()->GetCatalogVendorApp()->applicationId);
if (app.GetApplicationBasicDelegate()->GetCatalogVendorApp()->Matches(vendorApp))
@@ -395,6 +396,61 @@ ContentApp * ContentAppFactoryImpl::LoadContentApp(const CatalogVendorApp & vend
return nullptr;
}
+EndpointId ContentAppFactoryImpl::AddContentApp(ContentAppImpl & app)
+{
+ DataVersion dataVersionBuf[ArraySize(contentAppClusters)];
+ EndpointId epId = ContentAppPlatform::GetInstance().AddContentApp(&app, &contentAppEndpoint, Span(dataVersionBuf),
+ Span(gContentAppDeviceType));
+ ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl AddContentApp endpoint returned %d. Endpoint set %d", epId,
+ app.GetEndpointId());
+ mContentApps.push_back(app);
+ return epId;
+}
+
+/**
+ * @brief Code for testing the message flow path.
+ *
+ */
+class TestCommandHandlerCallback : public app::CommandHandler::Callback
+{
+ void OnDone(app::CommandHandler & apCommandObj) {}
+
+ void DispatchCommand(app::CommandHandler & apCommandObj, const app::ConcreteCommandPath & aCommandPath,
+ TLV::TLVReader & apPayload)
+ {}
+
+ Protocols::InteractionModel::Status CommandExists(const app::ConcreteCommandPath & aCommandPath)
+ {
+ return Protocols::InteractionModel::Status::Success;
+ }
+};
+
+/**
+ * @brief Code for testing the message flow path.
+ *
+ */
+void ContentAppFactoryImpl::SendTestMessage(EndpointId epId, const char * message)
+{
+ ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl SendTestMessage called with message %s & endpointId %d", message, epId);
+ for (size_t i = 0; i < mContentApps.size(); ++i)
+ {
+ ContentAppImpl app = mContentApps.at(i);
+ ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl checking app with endpointId %d", app.ContentApp::GetEndpointId());
+ if (app.GetEndpointId() == epId)
+ {
+ ChipLogProgress(DeviceLayer, "ContentAppFactoryImpl SendTestMessage endpoint found");
+ app::ConcreteCommandPath commandPath(epId, app::Clusters::ContentLauncher::Id,
+ app::Clusters::ContentLauncher::Commands::LaunchURL::Id);
+ chip::AppPlatform::TestCommandHandlerCallback callback;
+ app::CommandHandler commandHandler(&callback);
+ CommandResponseHelper helper(&commandHandler, commandPath);
+ chip::app::Clusters::ContentLauncher::Structs::BrandingInformation::Type branding;
+ app.GetContentLauncherDelegate()->HandleLaunchUrl(helper, CharSpan::fromCharString(message),
+ CharSpan::fromCharString("Temp Display"), branding);
+ }
+ }
+}
+
} // namespace AppPlatform
} // namespace chip
@@ -436,3 +492,17 @@ CHIP_ERROR PreServerInit()
return CHIP_NO_ERROR;
}
+
+EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
+ const char * szApplicationVersion, jobject manager)
+{
+ ContentAppImpl app =
+ ContentAppImpl(szVendorName, vendorId, szApplicationName, productId, szApplicationVersion, "34567890", manager);
+ ChipLogProgress(DeviceLayer, "AppImpl: AddContentApp vendorId=%d applicationName=%s ", vendorId, szApplicationName);
+ return gFactory.AddContentApp(app);
+}
+
+void SendTestMessage(EndpointId epID, const char * message)
+{
+ gFactory.SendTestMessage(epID, message);
+}
diff --git a/examples/tv-app/android/java/AppImpl.h b/examples/tv-app/android/java/AppImpl.h
index d7cb2b688db7ca..c1631bd80673fa 100644
--- a/examples/tv-app/android/java/AppImpl.h
+++ b/examples/tv-app/android/java/AppImpl.h
@@ -27,6 +27,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -37,6 +39,7 @@
#include "../include/target-navigator/TargetNavigatorManager.h"
#include "ChannelManager.h"
#include "CommissionerMain.h"
+#include "ContentAppCommandDelegate.h"
#include "KeypadInputManager.h"
#include "MediaPlaybackManager.h"
#include
@@ -50,6 +53,9 @@
CHIP_ERROR InitVideoPlayerPlatform();
CHIP_ERROR PreServerInit();
+EndpointId AddContentApp(const char * szVendorName, uint16_t vendorId, const char * szApplicationName, uint16_t productId,
+ const char * szApplicationVersion, jobject manager);
+void SendTestMessage(EndpointId epID, const char * message);
#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
@@ -65,6 +71,7 @@ using KeypadInputDelegate = app::Clusters::KeypadInput::Delegate;
using MediaPlaybackDelegate = app::Clusters::MediaPlayback::Delegate;
using TargetNavigatorDelegate = app::Clusters::TargetNavigator::Delegate;
using SupportedStreamingProtocol = app::Clusters::ContentLauncher::SupportedStreamingProtocol;
+using ContentAppCommandDelegate = chip::AppPlatform::ContentAppCommandDelegate;
static const int kCatalogVendorId = CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID;
@@ -75,10 +82,10 @@ 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) :
+ const char * szApplicationVersion, const char * setupPIN, jobject manager) :
mApplicationBasicDelegate(kCatalogVendorId, BuildAppId(vendorId), szVendorName, vendorId, szApplicationName, productId,
szApplicationVersion),
- mAccountLoginDelegate(setupPIN), mContentLauncherDelegate({ "image/*", "video/*" },
+ mAccountLoginDelegate(setupPIN), mContentLauncherDelegate(ContentAppCommandDelegate(manager), { "image/*", "video/*" },
to_underlying(SupportedStreamingProtocol::kDash) |
to_underlying(SupportedStreamingProtocol::kHls)),
mTargetNavigatorDelegate({ "home", "search", "info", "guide", "menu" }, 0){};
@@ -88,7 +95,11 @@ class DLL_EXPORT ContentAppImpl : public ContentApp
ApplicationBasicDelegate * GetApplicationBasicDelegate() override { return &mApplicationBasicDelegate; };
ApplicationLauncherDelegate * GetApplicationLauncherDelegate() override { return &mApplicationLauncherDelegate; };
ChannelDelegate * GetChannelDelegate() override { return &mChannelDelegate; };
- ContentLauncherDelegate * GetContentLauncherDelegate() override { return &mContentLauncherDelegate; };
+ ContentLauncherDelegate * GetContentLauncherDelegate() override
+ {
+ mContentLauncherDelegate.SetEndpointId(GetEndpointId());
+ return &mContentLauncherDelegate;
+ };
KeypadInputDelegate * GetKeypadInputDelegate() override { return &mKeypadInputDelegate; };
MediaPlaybackDelegate * GetMediaPlaybackDelegate() override { return &mMediaPlaybackDelegate; };
TargetNavigatorDelegate * GetTargetNavigatorDelegate() override { return &mTargetNavigatorDelegate; };
@@ -119,6 +130,10 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory
// Lookup ContentApp for this catalog id / app id and load it
ContentApp * LoadContentApp(const CatalogVendorApp & vendorApp) override;
+ EndpointId AddContentApp(ContentAppImpl & app);
+
+ void SendTestMessage(EndpointId epID, const char * message);
+
// Gets the catalog vendor ID used by this platform
uint16_t GetPlatformCatalogVendorId() override;
@@ -127,11 +142,11 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory
CHIP_ERROR ConvertToPlatformCatalogVendorApp(const CatalogVendorApp & sourceApp, CatalogVendorApp * destinationApp) override;
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{
+ ContentAppImpl("Vendor1", 1, "exampleid", 11, "Version1", "34567890", nullptr),
+ ContentAppImpl("Vendor2", 65521, "exampleString", 32768, "Version2", "20202021", nullptr),
+ ContentAppImpl("Vendor3", 9050, "App3", 22, "Version3", "20202021", nullptr),
+ ContentAppImpl("TestSuiteVendor", 1111, "applicationId", 22, "v2", "20202021", nullptr)
};
};
diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
new file mode 100644
index 00000000000000..a4d23881b6be3f
--- /dev/null
+++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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.
+ */
+
+/**
+ * @brief Contains Implementation of the ContentAppCommandDelegate
+ */
+
+#include "ContentAppCommandDelegate.h"
+
+#include
+#include
+#include
+#include
+
+namespace chip {
+namespace AppPlatform {
+
+const char * ContentAppCommandDelegate::sendCommand(chip::EndpointId epID, std::string commandPayload)
+{
+ // to support the hardcoded sample apps.
+ if (mSendCommandMethod == nullptr)
+ {
+ return "Failed";
+ }
+
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ UtfString jCommandPayload(env, commandPayload.c_str());
+ ChipLogProgress(Zcl, "ContentAppCommandDelegate::sendCommand with payload %s", commandPayload.c_str());
+ jstring resp = (jstring) env->CallObjectMethod(mContentAppEndpointManager, mSendCommandMethod, static_cast(epID),
+ jCommandPayload.jniValue());
+ if (env->ExceptionCheck())
+ {
+ ChipLogError(Zcl, "Java exception in ContentAppCommandDelegate::sendCommand");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ // TODO : Need to have proper errors passed back.
+ return "Failed";
+ }
+ const char * ret = env->GetStringUTFChars(resp, 0);
+ return ret;
+}
+
+} // namespace AppPlatform
+} // namespace chip
diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.h b/examples/tv-app/android/java/ContentAppCommandDelegate.h
new file mode 100644
index 00000000000000..2d45906c51049b
--- /dev/null
+++ b/examples/tv-app/android/java/ContentAppCommandDelegate.h
@@ -0,0 +1,72 @@
+/*
+ *
+ * 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.
+ */
+
+/**
+ * @brief Facilitates communication with the application handlers in Java
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace chip {
+namespace AppPlatform {
+
+class ContentAppCommandDelegate
+{
+public:
+ ContentAppCommandDelegate(jobject manager)
+ {
+
+ if (manager == nullptr)
+ {
+ // To support the existing hardcoded sample apps.
+ return;
+ }
+
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ VerifyOrReturn(env != nullptr, ChipLogError(Zcl, "Failed to GetEnvForCurrentThread for ContentAppEndpointManager"));
+
+ mContentAppEndpointManager = env->NewGlobalRef(manager);
+ VerifyOrReturn(mContentAppEndpointManager != nullptr,
+ ChipLogError(Zcl, "Failed to NewGlobalRef ContentAppEndpointManager"));
+
+ jclass ContentAppEndpointManagerClass = env->GetObjectClass(manager);
+ VerifyOrReturn(ContentAppEndpointManagerClass != nullptr,
+ ChipLogError(Zcl, "Failed to get ContentAppEndpointManager Java class"));
+
+ mSendCommandMethod =
+ env->GetMethodID(ContentAppEndpointManagerClass, "sendCommand", "(ILjava/lang/String;)Ljava/lang/String;");
+ if (mSendCommandMethod == nullptr)
+ {
+ ChipLogError(Zcl, "Failed to access ContentAppEndpointManager 'sendCommand' method");
+ env->ExceptionClear();
+ }
+ };
+
+ const char * sendCommand(chip::EndpointId epID, std::string commandPayload);
+
+private:
+ jobject mContentAppEndpointManager = nullptr;
+ jmethodID mSendCommandMethod = nullptr;
+};
+
+} // namespace AppPlatform
+} // namespace chip
diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp
index 765961a899963b..2c02e5f24f98c1 100644
--- a/examples/tv-app/android/java/TVApp-JNI.cpp
+++ b/examples/tv-app/android/java/TVApp-JNI.cpp
@@ -37,6 +37,7 @@
#include
#include
#include
+#include
using namespace chip;
using namespace chip::app;
@@ -175,3 +176,24 @@ JNI_METHOD(jboolean, setCurrentLevel)(JNIEnv *, jobject, jint endpoint, jboolean
{
return LevelManager::SetLevel(endpoint, value);
}
+
+JNI_METHOD(jint, addContentApp)
+(JNIEnv *, jobject, jstring vendorName, jint vendorId, jstring appName, jint productId, jstring appVersion, jobject manager)
+{
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+
+ JniUtfString vName(env, vendorName);
+ JniUtfString aName(env, appName);
+ JniUtfString aVersion(env, appVersion);
+ EndpointId epId = AddContentApp(vName.c_str(), static_cast(vendorId), aName.c_str(), static_cast(productId),
+ aVersion.c_str(), manager);
+ return static_cast(epId);
+}
+
+JNI_METHOD(void, sendTestMessage)(JNIEnv *, jobject, jint endpoint, jstring message)
+{
+ JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
+ const char * nmessage = env->GetStringUTFChars(message, 0);
+ ChipLogProgress(Zcl, "TvApp-JNI SendTestMessage called with message %s", nmessage);
+ SendTestMessage(static_cast(endpoint), nmessage);
+}
diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManager.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManager.java
new file mode 100644
index 00000000000000..09bf1f0a0f8b54
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManager.java
@@ -0,0 +1,6 @@
+package com.tcl.chip.tvapp;
+
+public interface ContentAppEndpointManager {
+
+ public String sendCommand(int endpointId, String commandPayload);
+}
diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManagerStub.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManagerStub.java
new file mode 100644
index 00000000000000..3de920ef95f938
--- /dev/null
+++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/ContentAppEndpointManagerStub.java
@@ -0,0 +1,15 @@
+package com.tcl.chip.tvapp;
+
+public class ContentAppEndpointManagerStub implements ContentAppEndpointManager {
+
+ ContentAppEndpointManager delegate;
+
+ public ContentAppEndpointManagerStub(ContentAppEndpointManager delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String sendCommand(int endpointId, String commandPayload) {
+ return delegate.sendCommand(endpointId, commandPayload);
+ }
+}
diff --git a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java
index 28f2f334126a0f..3b341a2a3ce8af 100644
--- a/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java
+++ b/examples/tv-app/android/java/src/com/tcl/chip/tvapp/TvApp.java
@@ -67,6 +67,16 @@ private void postClusterInit(int clusterId, int endpoint) {
public native void setDACProvider(DACProvider provider);
+ public native int addContentApp(
+ String vendorName,
+ int vendorId,
+ String appName,
+ int productId,
+ String appVersion,
+ ContentAppEndpointManager manager);
+
+ public native void sendTestMessage(int endpoint, String message);
+
static {
System.loadLibrary("TvApp");
}