diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl
index 7db26283d00de1..72a6cc2a89da83 100644
--- a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl
+++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/SupportedCluster.aidl
@@ -5,8 +5,9 @@ parcelable SupportedCluster {
int clusterIdentifier;
- String[] features;
+ int features;
int[] optionalCommandIdentifiers;
+ int[] optionalAttributesIdentifiers;
}
\ No newline at end of file
diff --git a/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java b/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java
index dbb80b1e884933..93cd816eb95984 100644
--- a/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java
+++ b/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java
@@ -1,28 +1,260 @@
package com.matter.tv.app.api;
+/**
+ * Helper class to hold the IDs and corresponding constants for media related clusters. TODO : Add
+ * the rest of the media clusters TODO : Maybe generate using ZAP tool
+ */
public class Clusters {
// Clusters
public static class MediaPlayback {
- public static final int Id = 1286;
+ public static final int Id = 0x0506;
+
+ public static class Commands {
+ public static class Play {
+ public static final int ID = 0x00;
+ }
+
+ public static class Pause {
+ public static final int ID = 0x01;
+ }
+
+ public static class StopPlayback {
+ public static final int ID = 0x02;
+ }
+
+ public static class StartOver {
+ public static final int ID = 0x03;
+ }
+
+ public static class Previous {
+ public static final int ID = 0x04;
+ }
+
+ public static class Next {
+ public static final int ID = 0x05;
+ }
+
+ public static class Rewind {
+ public static final int ID = 0x06;
+ }
+
+ public static class FastForward {
+ public static final int ID = 0x07;
+ }
+
+ public static class SkipForward {
+ public static final int ID = 0x08;
+
+ public static class Fields {
+ public static final int DeltaPositionMilliseconds = 0x00;
+ }
+ }
+
+ public static class SkipBackward {
+ public static final int ID = 0x09;
+
+ public static class Fields {
+ public static final int DeltaPositionMilliseconds = 0x00;
+ }
+ }
+
+ public static class Seek {
+ public static final int ID = 0x0B;
+
+ public static class Fields {
+ public static final int Position = 0x00;
+ }
+ }
+
+ public static class PlaybackResponse {
+ public static final int ID = 0x0A;
+
+ public static class Fields {
+ public static final int Status = 0x00;
+ public static final int Data = 0x01;
+ }
+ }
+ }
public static class Attributes {
- public static final int CurrentState = 0;
+ public static final int CurrentState = 0x00;
+ public static final int StartTime = 0x01;
+ public static final int Duration = 0x02;
+ public static final int SampledPosition = 0x03;
+ public static final int PlaybackSpeed = 0x04;
+ public static final int SeekRangeEnd = 0x05;
+ public static final int SeekRangeStart = 0x06;
}
- public static class PlaybackStateEnum {
- public static final int Playing = 0;
- public static final int Paused = 1;
- public static final int NotPlaying = 2;
- public static final int Buffering = 3;
+ public static class Types {
+ public static class PlaybackStateEnum {
+ public static final int Playing = 0x00;
+ public static final int Paused = 0x01;
+ public static final int NotPlaying = 0x02;
+ public static final int Buffering = 0x03;
+ }
+
+ public static class StatusEnum {
+ public static final int Success = 0x00;
+ public static final int InvalidStateForCommand = 0x01;
+ public static final int NotAllowed = 0x02;
+ public static final int NotActive = 0x03;
+ public static final int SpeedOutOfRange = 0x04;
+ public static final int SeekOutOfRange = 0x05;
+ }
+
+ public static class PlaybackPosition {
+ public static final int UpdatedAt = 0x00;
+ public static final int Position = 0x01;
+ }
}
}
public static class ContentLauncher {
- public static final int Id = 1290;
+ public static final int Id = 0x050A;
+
+ public static class Commands {
+ public static class LaunchContent {
+ public static final int ID = 0x00;
+
+ public static class Fields {
+ public static final int Search = 0x00;
+ public static final int AutoPlay = 0x01;
+ public static final int Data = 0x02;
+ }
+ }
+
+ public static class LaunchURL {
+ public static final int ID = 0x01;
+
+ public static class Fields {
+ public static final int ContentURL = 0x00;
+ public static final int DisplayString = 0x01;
+ public static final int BrandingInformation = 0x02;
+ }
+ }
+
+ public static class LaunchResponse {
+ public static final int ID = 0x02;
+
+ public static class Fields {
+ public static final int Status = 0x00;
+ public static final int Data = 0x01;
+ }
+ }
+ }
+
+ public static class Attributes {
+ public static final int AcceptHeader = 0x00;
+ public static final int SupportedStreamingProtocols = 0x01;
+ }
+
+ public static class Types {
+ public static class ContentSearch {
+ public static final int ParameterList = 0x00;
+ }
+
+ public static class StatusEnum {
+ public static final int Success = 0x00;
+ public static final int UrlNotAvailable = 0x01;
+ public static final int AuthFailed = 0x02;
+ }
+
+ public static class Parameter {
+ public static final int Type = 0x00;
+ public static final int Value = 0x01;
+ public static final int ExternalIDList = 0x02;
+ }
+
+ public static class ParameterEnum {
+ public static final int Actor = 0x00;
+ public static final int Channel = 0x01;
+ public static final int Character = 0x02;
+ public static final int Director = 0x03;
+ public static final int Event = 0x04;
+ public static final int Franchise = 0x05;
+ public static final int Genre = 0x06;
+ public static final int League = 0x07;
+ public static final int Popularity = 0x08;
+ public static final int Provider = 0x09;
+ public static final int Sport = 0x0A;
+ public static final int SportsTeam = 0x0B;
+ public static final int Type = 0x0C;
+ public static final int Video = 0x0D;
+ }
+
+ public static class AdditionalInfo {
+ public static final int Name = 0x00;
+ public static final int Value = 0x01;
+ }
+
+ public static class BrandingInformation {
+ public static final int ProviderName = 0x00;
+ public static final int Background = 0x01;
+ public static final int Logo = 0x02;
+ public static final int ProgressBar = 0x03;
+ public static final int Splash = 0x04;
+ public static final int WaterMark = 0x05;
+ }
+
+ public static class StyleInformation {
+ public static final int ProviderName = 0x00;
+ public static final int Background = 0x01;
+ public static final int Logo = 0x02;
+ }
+
+ public static class Dimension {
+ public static final int ImageUrl = 0x00;
+ public static final int Color = 0x01;
+ public static final int Size = 0x02;
+ }
+
+ public static class MetricTypeEnum {
+ public static final int Pixels = 0x00;
+ public static final int Percentage = 0x01;
+ }
+ }
+ }
+
+ public static class TargetNavigator {
+ public static final int Id = 0x0505;
+
+ public static class Commands {
+ public static class NavigateTarget {
+ public static final int ID = 0x00;
+
+ public static class Fields {
+ public static final int Target = 0x00;
+ public static final int Data = 0x01;
+ }
+ }
+
+ public static class NavigateTargetResponse {
+ public static final int ID = 0x01;
+
+ public static class Fields {
+ public static final int Status = 0x00;
+ public static final int Data = 0x01;
+ }
+ }
+ }
public static class Attributes {
- public static final int AcceptHeader = 0;
- public static final int SupportedStreamingProtocols = 1;
+ public static final int TargetList = 0x00;
+ public static final int CurrentTarget = 0x01;
+ }
+
+ public static class Types {
+ public static class TargetInfo {
+ public static final int Identifier = 0x00;
+ public static final int Name = 0x01;
+ }
+
+ public static class StatusEnum {
+ public static final int Success = 0x00;
+ public static final int TargetNotFound = 0x01;
+ public static final int NotAllowed = 0x02;
+ }
}
}
}
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 e9fa3b028f6f58..61d036e0a90ef4 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
@@ -25,8 +25,8 @@
android:exported="true">
-
+
> attributeValues = new HashMap<>();
- private AttributeHolder() {};
+ private AttributeHolder() {
+ // Setting up attribute defaults
+ setAttributeValue(
+ Clusters.ContentLauncher.Id,
+ Clusters.ContentLauncher.Attributes.AcceptHeader,
+ "[\"video/mp4\", \"application/x-mpegURL\", \"application/dash+xml\"]");
+ setAttributeValue(
+ Clusters.ContentLauncher.Id,
+ Clusters.ContentLauncher.Attributes.SupportedStreamingProtocols,
+ 3);
+ setAttributeValue(Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState, 2);
+ setAttributeValue(
+ Clusters.TargetNavigator.Id,
+ Clusters.TargetNavigator.Attributes.TargetList,
+ "[{\"0\":1, \"1\":\"Home\"},{\"0\":2, \"1\":\"Settings\"},{\"0\":3, \"1\":\"Casting Home\"}]");
+ setAttributeValue(
+ Clusters.TargetNavigator.Id, Clusters.TargetNavigator.Attributes.CurrentTarget, 1);
+ };
public static AttributeHolder getInstance() {
return instance;
diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java
index c346a5c1ea4b82..a5d6d706e35f34 100644
--- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java
+++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/MainActivity.java
@@ -49,7 +49,7 @@ protected void onCreate(Bundle savedInstanceState) {
.setAttributeValue(
Clusters.MediaPlayback.Id,
Clusters.MediaPlayback.Attributes.CurrentState,
- Clusters.MediaPlayback.PlaybackStateEnum.Playing);
+ Clusters.MediaPlayback.Types.PlaybackStateEnum.Playing);
reportAttributeChange(
Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState);
break;
@@ -58,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) {
.setAttributeValue(
Clusters.MediaPlayback.Id,
Clusters.MediaPlayback.Attributes.CurrentState,
- Clusters.MediaPlayback.PlaybackStateEnum.Paused);
+ Clusters.MediaPlayback.Types.PlaybackStateEnum.Paused);
reportAttributeChange(
Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState);
break;
@@ -67,7 +67,7 @@ protected void onCreate(Bundle savedInstanceState) {
.setAttributeValue(
Clusters.MediaPlayback.Id,
Clusters.MediaPlayback.Attributes.CurrentState,
- Clusters.MediaPlayback.PlaybackStateEnum.Buffering);
+ Clusters.MediaPlayback.Types.PlaybackStateEnum.Buffering);
reportAttributeChange(
Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState);
break;
@@ -76,7 +76,7 @@ protected void onCreate(Bundle savedInstanceState) {
.setAttributeValue(
Clusters.MediaPlayback.Id,
Clusters.MediaPlayback.Attributes.CurrentState,
- Clusters.MediaPlayback.PlaybackStateEnum.NotPlaying);
+ Clusters.MediaPlayback.Types.PlaybackStateEnum.NotPlaying);
reportAttributeChange(
Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState);
break;
@@ -91,21 +91,6 @@ protected void onCreate(Bundle savedInstanceState) {
MatterAgentClient matterAgentClient = MatterAgentClient.getInstance();
executorService.execute(matterAgentClient::reportClusters);
-
- // Setting up attribute defaults
- AttributeHolder.getInstance()
- .setAttributeValue(
- Clusters.ContentLauncher.Id,
- Clusters.ContentLauncher.Attributes.AcceptHeader,
- "[\"video/mp4\", \"application/x-mpegURL\", \"application/dash+xml\"]");
- AttributeHolder.getInstance()
- .setAttributeValue(
- Clusters.ContentLauncher.Id,
- Clusters.ContentLauncher.Attributes.SupportedStreamingProtocols,
- 3);
- AttributeHolder.getInstance()
- .setAttributeValue(
- Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState, 2);
}
private void reportAttributeChange(final int clusterId, final int attributeId) {
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 930dc63d26f43a..c8d8987048be76 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
@@ -43,6 +43,7 @@ public void onReceive(Context context, Intent intent) {
Intent in = new Intent(context, MainActivity.class);
in.putExtra(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD, command);
+ in.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(in);
Log.d(TAG, "Started activity. Now sending response");
diff --git a/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml b/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml
index cfa74283d71759..04b30f27ad639d 100644
--- a/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml
+++ b/examples/tv-app/android/App/platform-app/src/main/AndroidManifest.xml
@@ -39,8 +39,9 @@
android:launchMode="singleTask">
-
+
+
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
index 0b9ea344b161ad..6f4e6eb98c52e2 100644
--- 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
@@ -6,6 +6,8 @@
import com.matter.tv.server.receivers.ContentAppDiscoveryService;
import com.matter.tv.server.service.ContentAppAgentService;
import com.matter.tv.server.tvapp.ContentAppEndpointManager;
+import com.matter.tv.server.utils.EndpointsDataStore;
+import java.util.Collection;
public class ContentAppEndpointManagerImpl implements ContentAppEndpointManager {
@@ -18,15 +20,37 @@ public ContentAppEndpointManagerImpl(Context context) {
public String sendCommand(int endpointId, int clusterId, int commandId, 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);
- return ContentAppAgentService.sendCommand(
- context, app.getAppName(), clusterId, commandId, commandPayload);
- }
+
+ ContentApp discoveredApp =
+ getContentApp(
+ ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values(),
+ endpointId);
+ if (discoveredApp != null) {
+ Log.d(TAG, "Sending a command for endpointId " + endpointId + ". Message " + commandPayload);
+ return ContentAppAgentService.sendCommand(
+ context, discoveredApp.getAppName(), clusterId, commandId, commandPayload);
}
+
+ // check to see if this was a previously discovered but now removed app
+ ContentApp persistedApp =
+ getContentApp(
+ EndpointsDataStore.getInstance(context).getAllPersistedContentApps().values(),
+ endpointId);
+ if (persistedApp != null) {
+ Log.d(
+ TAG,
+ "Message received for a previously discovered app that is no longer "
+ + "available. App Name "
+ + persistedApp.getAppName());
+ return "{\""
+ + ContentAppAgentService.FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + ContentAppAgentService.FAILED_UNSUPPORTED_ENDPOINT
+ + "}}";
+ }
+ // For test cases to pass.
return "Success";
}
@@ -39,14 +63,44 @@ public String readAttribute(int endpointId, int clusterId, int attributeId) {
+ clusterId
+ " attributeId "
+ attributeId);
- for (ContentApp app :
- ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values()) {
+ ContentApp discoveredApp =
+ getContentApp(
+ ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values(),
+ endpointId);
+ if (discoveredApp != null) {
+ Log.d(TAG, "Sending attribute read request for endpointId " + endpointId);
+ return ContentAppAgentService.sendAttributeReadRequest(
+ context, discoveredApp.getAppName(), clusterId, attributeId);
+ }
+ // check to see if this was a previously discovered but now removed app
+ ContentApp persistedApp =
+ getContentApp(
+ EndpointsDataStore.getInstance(context).getAllPersistedContentApps().values(),
+ endpointId);
+ if (persistedApp != null) {
+ Log.d(
+ TAG,
+ "Message received for a previously discovered app that is no longer "
+ + "available. App Name "
+ + persistedApp.getAppName());
+ return "{\""
+ + ContentAppAgentService.FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + ContentAppAgentService.FAILED_UNSUPPORTED_ENDPOINT
+ + "}}";
+ }
+ // For test cases to pass.
+ return "";
+ }
+
+ ContentApp getContentApp(Collection apps, int endpointId) {
+ for (ContentApp app : apps) {
if (app.getEndpointId() == endpointId) {
- Log.d(TAG, "Sending attribute read request for endpointId " + endpointId);
- return ContentAppAgentService.sendAttributeReadRequest(
- context, app.getAppName(), clusterId, attributeId);
+ return app;
}
}
- return "";
+ return null;
}
}
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
index 7dd5f112cc8855..6b5856870eba58 100644
--- 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
@@ -9,6 +9,7 @@
public class ContentApp {
public static final int INVALID_ENDPOINTID = -1;
+ private final String version;
private String appName;
private String vendorName;
private int vendorId;
@@ -17,16 +18,27 @@ public class ContentApp {
// initially set to an invalid value.
private int endpoint = INVALID_ENDPOINTID;
+ public ContentApp(
+ String appName, String vendorName, int vendorId, int productId, String version) {
+ this.vendorName = vendorName;
+ this.appName = appName;
+ this.vendorId = vendorId;
+ this.productId = productId;
+ this.version = version;
+ }
+
public ContentApp(
String appName,
String vendorName,
int vendorId,
int productId,
+ String version,
Set supportedClusters) {
this.vendorName = vendorName;
this.appName = appName;
this.vendorId = vendorId;
this.productId = productId;
+ this.version = version;
this.supportedClusters = supportedClusters;
}
@@ -58,6 +70,10 @@ public Set getSupportedClusters() {
return Collections.unmodifiableSet(supportedClusters);
}
+ public String getVersion() {
+ return version;
+ }
+
public void setSupportedClusters(List supportedClusters) {
this.supportedClusters = new HashSet<>(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 ebdc10acd3a557..ba654f3a7227d1 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
@@ -5,9 +5,11 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.matter.tv.app.api.MatterIntentConstants;
@@ -90,11 +92,18 @@ private void handlePackageAdded(Context context, String pkg) {
if (appInfo.metaData == null) {
return;
}
+ PackageInfo packageInfo = pm.getPackageInfo(pkg, PackageManager.GET_META_DATA);
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, "");
+ String version;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ version = String.valueOf(packageInfo.getLongVersionCode());
+ } else {
+ version = String.valueOf(packageInfo.versionName);
+ }
if (vendorId == -1 || productId == -1) {
return;
@@ -109,7 +118,8 @@ private void handlePackageAdded(Context context, String pkg) {
supportedClusters = new HashSet<>();
}
- ContentApp app = new ContentApp(pkg, vendorName, vendorId, productId, supportedClusters);
+ ContentApp app =
+ new ContentApp(pkg, vendorName, vendorId, productId, version, supportedClusters);
applications.put(pkg, app);
Intent in = new Intent(DISCOVERY_APPAGENT_ACTION_ADD);
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/AppPlatformService.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/AppPlatformService.java
index a841fece436bff..d5d634015366c5 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/AppPlatformService.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/AppPlatformService.java
@@ -22,13 +22,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
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.utils.EndpointsDataStore;
import java.util.HashMap;
import java.util.Map;
@@ -42,10 +42,9 @@
public class AppPlatformService {
private static final String TAG = "AppPlatformService";
- private static final String MATTER_APPPLATFORM_ENDPOINTS = "matter.appplatform.endpoints";
private AppPlatform mAppPlatform;
private BroadcastReceiver mBroadcastReceiver;
- private SharedPreferences discoveredEndpoints;
+ private EndpointsDataStore endpointsDataStore;
private AppPlatformService() {}
@@ -66,27 +65,48 @@ public static AppPlatformService get() {
public void init(@NonNull Context context) {
this.context = context;
- discoveredEndpoints =
- context.getSharedPreferences(MATTER_APPPLATFORM_ENDPOINTS, Context.MODE_PRIVATE);
- mAppPlatform = new AppPlatform(new ContentAppEndpointManagerImpl(context));
ContentAppDiscoveryService.getReceiverInstance().registerSelf(context.getApplicationContext());
- Map previouslyPersistedEndpoints = new HashMap();
- previouslyPersistedEndpoints.putAll((Map) discoveredEndpoints.getAll());
- for (ContentApp app :
- ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps().values()) {
- addContentApp(app);
- previouslyPersistedEndpoints.remove(app.getAppName());
+ mAppPlatform = new AppPlatform(new ContentAppEndpointManagerImpl(context));
+ endpointsDataStore = EndpointsDataStore.getInstance(context);
+ initializeContentAppEndpoints();
+ registerContentAppUpdatesReceiver();
+ }
+
+ private void initializeContentAppEndpoints() {
+
+ // Read the metadada of previously discovered endpoints.
+ Map previouslyPersistedEndpoints =
+ new HashMap((Map) endpointsDataStore.getAllPersistedContentApps());
+
+ // Get the list of currently discovered content apps.
+ Map discoveredContentApps =
+ new HashMap<>(ContentAppDiscoveryService.getReceiverInstance().getDiscoveredContentApps());
+
+ // Iterate through the previously discovered endpoints
+ for (ContentApp persistedContentApp : previouslyPersistedEndpoints.values()) {
+ ContentApp discoveredContentApp =
+ discoveredContentApps.remove(persistedContentApp.getAppName());
+ if (discoveredContentApp != null) {
+ // If the content app for the persisted endpoint is present in currently discovered list,
+ // register endpoint with updated metadata and update the metadata in the persisted
+ // endpoints.
+ discoveredContentApp.setEndpointId(persistedContentApp.getEndpointId());
+ addContentApp(discoveredContentApp);
+ } else {
+ // If the content app for the persisted endpoint is not present register the endpoint with
+ // previously persisted data.
+ addContentApp(persistedContentApp);
+ }
}
- SharedPreferences.Editor editor = discoveredEndpoints.edit();
- for (Map.Entry appEntry : previouslyPersistedEndpoints.entrySet()) {
- editor.remove(appEntry.getKey());
- // TODO : Figure out how to cleanup ACLs
+
+ // For newly discovered content apps register new endpoints and persist the metadata for future
+ // use
+ for (ContentApp discoveredContentApp : discoveredContentApps.values()) {
+ addContentApp(discoveredContentApp);
}
- editor.apply();
- registerReceiver();
}
- private void registerReceiver() {
+ private void registerContentAppUpdatesReceiver() {
mBroadcastReceiver =
new BroadcastReceiver() {
@Override
@@ -100,14 +120,24 @@ public void onReceive(Context context, Intent intent) {
ContentAppDiscoveryService.getReceiverInstance()
.getDiscoveredContentApps()
.get(packageName);
- addContentApp(app);
- } else if (action.equals(ContentAppDiscoveryService.DISCOVERY_APPAGENT_ACTION_REMOVE)) {
- int endpointId =
- intent.getIntExtra(
- ContentAppDiscoveryService.DISCOVERY_APPAGENT_EXTRA_ENDPOINTID, -1);
- if (endpointId != -1) {
- removeContentApp(endpointId, packageName);
+ // if this app was already added as endpoint remove and add so that the app metadata
+ // stored by the content app platform is also updated
+ ContentApp persistedContentApp =
+ endpointsDataStore.getAllPersistedContentApps().get(app.getAppName());
+ if (persistedContentApp != null) {
+ mAppPlatform.removeContentApp(persistedContentApp.getEndpointId());
+ app.setEndpointId(persistedContentApp.getEndpointId());
}
+ addContentApp(app);
+ // } else if
+ // (action.equals(ContentAppDiscoveryService.DISCOVERY_APPAGENT_ACTION_REMOVE)) {
+ // int endpointId =
+ // intent.getIntExtra(
+ //
+ // ContentAppDiscoveryService.DISCOVERY_APPAGENT_EXTRA_ENDPOINTID, -1);
+ // if (endpointId != -1) {
+ // removeContentApp(endpointId, packageName);
+ // }
}
}
};
@@ -125,7 +155,7 @@ public void setActivity(Activity activity) {
public void addContentApp(ContentApp app) {
int retEndpointId = -1;
- int desiredEndpointId = discoveredEndpoints.getInt(app.getAppName(), -1);
+ int desiredEndpointId = app.getEndpointId();
if (desiredEndpointId > 0) {
retEndpointId =
mAppPlatform.addContentAppAtEndpoint(
@@ -133,7 +163,7 @@ public void addContentApp(ContentApp app) {
app.getVendorId(),
app.getAppName(),
app.getProductId(),
- "1.0",
+ app.getVersion(),
desiredEndpointId,
new ContentAppEndpointManagerImpl(context));
} else {
@@ -143,22 +173,14 @@ public void addContentApp(ContentApp app) {
app.getVendorId(),
app.getAppName(),
app.getProductId(),
- "1.0",
+ app.getVersion(),
new ContentAppEndpointManagerImpl(context));
}
if (retEndpointId > 0) {
app.setEndpointId(retEndpointId);
- discoveredEndpoints.edit().putInt(app.getAppName(), app.getEndpointId()).apply();
+ endpointsDataStore.persistContentAppEndpoint(app);
} else {
Log.e(TAG, "Could not add content app as endpoint. App Name " + app.getAppName());
}
}
-
- public int removeContentApp(int endpointID, String appName) {
- int retEndpointId = mAppPlatform.removeContentApp(endpointID);
- if (endpointID == retEndpointId) {
- discoveredEndpoints.edit().remove(appName).apply();
- }
- return retEndpointId;
- }
}
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 6fdfa6a0b3ff5e..a9a5e2af7d6b47 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
@@ -9,6 +9,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.matter.tv.app.api.IMatterAppAgent;
import com.matter.tv.app.api.MatterIntentConstants;
@@ -24,6 +25,18 @@ public class ContentAppAgentService extends Service {
public static final String EXTRA_RESPONSE_RECEIVING_PACKAGE = "EXTRA_RESPONSE_RECEIVING_PACKAGE";
public static final String EXTRA_RESPONSE_ID = "EXTRA_RESPONSE_ID";
+ public static final String FAILURE_KEY = "PlatformError";
+ public static final String FAILURE_STATUS_KEY = "Status";
+ public static final int FAILED_UNSUPPORTED_ENDPOINT = 0x7f;
+ public static final int FAILED_UNSUPPORTED_CLUSTER = 0xc3;
+ public static final int FAILED_UNSUPPORTED_COMMAND = 0x81;
+ public static final int FAILED_UNSUPPORTED_ATTRIBUTE = 0x86;
+ public static final int FAILED_UNKNOWN = 0x01;
+ public static final int FAILED_TIMEOUT = 0x94;
+
+ private static final int COMMAND_TIMEOUT = 8; // seconds
+ private static final int ATTRIBUTE_TIMEOUT = 2; // seconds
+
private static ResponseRegistry responseRegistry = new ResponseRegistry();
private final IBinder appAgentBinder =
@@ -100,13 +113,7 @@ public static String sendCommand(
MatterIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT,
getPendingIntentForResponse(context, packageName, messageId));
context.sendBroadcast(in);
- responseRegistry.waitForMessage(messageId, 10, TimeUnit.SECONDS);
- String response = responseRegistry.readAndRemoveResponse(messageId);
- if (response == null) {
- response = "";
- }
- Log.d(TAG, "Response " + response + " being returned for message " + messageId);
- return response;
+ return getResponse(messageId, COMMAND_TIMEOUT);
}
public static String sendAttributeReadRequest(
@@ -127,10 +134,61 @@ public static String sendAttributeReadRequest(
MatterIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT,
getPendingIntentForResponse(context, packageName, messageId));
context.sendBroadcast(in);
- responseRegistry.waitForMessage(messageId, 10, TimeUnit.SECONDS);
- String response = responseRegistry.readAndRemoveResponse(messageId);
- if (response == null) {
- response = "";
+ return getResponse(messageId, ATTRIBUTE_TIMEOUT);
+ }
+
+ @NonNull
+ private static String getResponse(int messageId, int timeout) {
+ ResponseRegistry.WaitState status =
+ responseRegistry.waitForMessage(messageId, timeout, TimeUnit.SECONDS);
+ String response = "";
+ switch (status) {
+ case SUCCESS:
+ case INVALID_COUNTER:
+ response = responseRegistry.readAndRemoveResponse(messageId);
+ if (response == null) {
+ response =
+ "{\""
+ + FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + FAILED_UNKNOWN
+ + "}}";
+ }
+ break;
+ case TIMED_OUT:
+ response =
+ "{\""
+ + FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + FAILED_TIMEOUT
+ + "}}";
+ break;
+ case INTERRUPTED:
+ response = responseRegistry.readAndRemoveResponse(messageId);
+ if (response == null) {
+ response =
+ "{\""
+ + FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + FAILED_TIMEOUT
+ + "}}";
+ }
+ break;
+ default:
+ response =
+ "{\""
+ + FAILURE_KEY
+ + "\":{\""
+ + ContentAppAgentService.FAILURE_STATUS_KEY
+ + "\":"
+ + FAILED_UNKNOWN
+ + "}}";
}
Log.d(TAG, "Response " + response + " being returned for message " + messageId);
return response;
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ResponseRegistry.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ResponseRegistry.java
index 9c1ebc0c0b6031..1056cf493906af 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ResponseRegistry.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/service/ResponseRegistry.java
@@ -9,6 +9,13 @@
public class ResponseRegistry {
+ public enum WaitState {
+ TIMED_OUT,
+ INTERRUPTED,
+ SUCCESS,
+ INVALID_COUNTER
+ }
+
private static final String TAG = "ResponseRegistry";
private AtomicInteger messageCounter = new AtomicInteger();
@@ -27,18 +34,21 @@ public int getNextMessageCounter() {
return counter;
}
- public void waitForMessage(int counter, long timeout, TimeUnit unit) {
+ public WaitState waitForMessage(int counter, long timeout, TimeUnit unit) {
CountDownLatch latch = latches.get(counter);
if (latch == null) {
- return;
+ return WaitState.INVALID_COUNTER;
}
try {
if (!latch.await(timeout, unit)) {
Log.i(TAG, "Timed out while waiting for response for message " + counter);
+ return WaitState.TIMED_OUT;
}
} catch (InterruptedException e) {
Log.i(TAG, "Interrupted while waiting for response for message " + counter);
+ return WaitState.INTERRUPTED;
}
+ return WaitState.SUCCESS;
}
public void receivedMessageResponse(int counter, String response) {
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java
new file mode 100644
index 00000000000000..f587905fb09145
--- /dev/null
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/utils/EndpointsDataStore.java
@@ -0,0 +1,119 @@
+package com.matter.tv.server.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import com.matter.tv.server.model.ContentApp;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class EndpointsDataStore {
+
+ private static final String MATTER_APPPLATFORM_ENDPOINTS_PREF = "matter.appplatform.endpoints";
+ private static final String KEY_NAME = "N";
+ private static final String KEY_VENDORID = "VID";
+ private static final String KEY_VENDORNAME = "VN";
+ private static final String KEY_PRODUCTID = "PID";
+ private static final String KEY_VERSION = "VER";
+ private static final String KEY_ENDPOINTID = "EPID";
+ private static EndpointsDataStore instance;
+ private final SharedPreferences discoveredEndpoints;
+ Map persistedContentApps = new HashMap<>();
+
+ private EndpointsDataStore(Context context) {
+ discoveredEndpoints =
+ context.getSharedPreferences(MATTER_APPPLATFORM_ENDPOINTS_PREF, Context.MODE_PRIVATE);
+ // Take care of versioning when required
+ Map appMetadata = (Map) discoveredEndpoints.getAll();
+ for (Map.Entry contentAppEntry : appMetadata.entrySet()) {
+ persistedContentApps.put(
+ contentAppEntry.getKey(),
+ deserializeContentApp(contentAppEntry.getKey(), contentAppEntry.getValue()));
+ }
+ }
+
+ public static synchronized EndpointsDataStore getInstance(Context context) {
+ if (instance == null) {
+ instance = new EndpointsDataStore(context);
+ }
+ return instance;
+ }
+
+ public Map getAllPersistedContentApps() {
+ return Collections.unmodifiableMap(persistedContentApps);
+ }
+
+ public void persistContentAppEndpoint(ContentApp app) {
+ persistedContentApps.put(app.getAppName(), app);
+ discoveredEndpoints.edit().putString(app.getAppName(), serializeContentApp(app)).apply();
+ }
+
+ private String serializeContentApp(ContentApp app) {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ try {
+ jsonWriter
+ .beginObject()
+ .name(KEY_VENDORID)
+ .value(app.getVendorId())
+ .name(KEY_VENDORNAME)
+ .value(app.getVendorName())
+ .name(KEY_PRODUCTID)
+ .value(app.getProductId())
+ .name(KEY_VERSION)
+ .value(app.getVersion())
+ .name(KEY_ENDPOINTID)
+ .value(app.getEndpointId())
+ .endObject();
+ jsonWriter.flush();
+ jsonWriter.close();
+ } catch (IOException e) {
+ // Cannot happen
+ }
+ return stringWriter.toString();
+ }
+
+ private ContentApp deserializeContentApp(String appName, String appMetadata) {
+ JsonReader jsonReader = new JsonReader(new StringReader(appMetadata));
+ ContentApp app = null;
+ try {
+ jsonReader.beginObject();
+ String version = "";
+ String vendorName = "";
+ int vendorId = 0;
+ int productId = 0;
+ int endpoint = ContentApp.INVALID_ENDPOINTID;
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ switch (name) {
+ case KEY_VENDORID:
+ vendorId = jsonReader.nextInt();
+ break;
+ case KEY_VENDORNAME:
+ vendorName = jsonReader.nextString();
+ break;
+ case KEY_PRODUCTID:
+ productId = jsonReader.nextInt();
+ break;
+ case KEY_VERSION:
+ version = jsonReader.nextString();
+ break;
+ case KEY_ENDPOINTID:
+ endpoint = jsonReader.nextInt();
+ break;
+ }
+ }
+ app = new ContentApp(appName, vendorName, vendorId, productId, version);
+ jsonReader.endObject();
+ jsonReader.close();
+ } catch (IOException e) {
+ // Cannot happen
+ }
+ return app;
+ }
+}
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 71ccc02f1799b2..1a04c5294d2f30 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
@@ -18,7 +18,9 @@ public class 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_FEATURE_FLAGS = "featureFlags";
private static final String KEY_OPTIONAL_COMMANDS = "optionalCommands";
+ private static final String KEY_OPTIONAL_ATTRIBUTES = "optionalAttributes";
private ResourceUtils() {}
@@ -58,14 +60,8 @@ public Set getSupportedClusters(final Resources resources, fin
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_FEATURE_FLAGS)) {
+ cluster.features = reader.nextInt();
} else if (name.equals(KEY_OPTIONAL_COMMANDS)) {
List commands = new ArrayList<>();
reader.beginArray();
@@ -79,6 +75,19 @@ public Set getSupportedClusters(final Resources resources, fin
commandIds[i++] = command;
}
cluster.optionalCommandIdentifiers = commandIds;
+ } else if (name.equals(KEY_OPTIONAL_ATTRIBUTES)) {
+ List attributes = new ArrayList<>();
+ reader.beginArray();
+ while (reader.hasNext()) {
+ attributes.add(reader.nextInt());
+ }
+ reader.endArray();
+ int[] attributeIds = new int[attributes.size()];
+ int i = 0;
+ for (Integer command : attributes) {
+ attributeIds[i++] = command;
+ }
+ cluster.optionalAttributesIdentifiers = attributeIds;
} else {
reader.skipValue();
}
diff --git a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp
index ec9b24598acd3d..5bb39495b0a703 100644
--- a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp
+++ b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.cpp
@@ -16,12 +16,16 @@
*/
#include "TargetNavigatorManager.h"
+#include
using namespace std;
using namespace chip::app;
using namespace chip::app::Clusters::TargetNavigator;
+using ContentAppAttributeDelegate = chip::AppPlatform::ContentAppAttributeDelegate;
-TargetNavigatorManager::TargetNavigatorManager(std::list targets, uint8_t currentTarget)
+TargetNavigatorManager::TargetNavigatorManager(ContentAppAttributeDelegate * attributeDelegate, std::list targets,
+ uint8_t currentTarget) :
+ mAttributeDelegate(attributeDelegate)
{
mTargets = targets;
mCurrentTarget = currentTarget;
@@ -29,6 +33,54 @@ TargetNavigatorManager::TargetNavigatorManager(std::list targets, u
CHIP_ERROR TargetNavigatorManager::HandleGetTargetList(AttributeValueEncoder & aEncoder)
{
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleNavigateTarget");
+
+ if (mAttributeDelegate != nullptr)
+ {
+ chip::app::ConcreteReadAttributePath aPath(mEndpointId, chip::app::Clusters::TargetNavigator::Id,
+ chip::app::Clusters::TargetNavigator::Attributes::TargetList::Id);
+ const char * resStr = mAttributeDelegate->Read(aPath);
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleNavigateTarget response %s", resStr);
+
+ if (resStr != nullptr && *resStr != 0)
+ {
+ Json::Reader reader;
+ Json::Value value;
+ if (reader.parse(resStr, value))
+ {
+ std::string attrId = to_string(chip::app::Clusters::TargetNavigator::Attributes::TargetList::Id);
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleNavigateTarget response parsing done. reading attr %s",
+ attrId.c_str());
+ if (value[attrId].isArray())
+ {
+ return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
+ int i = 0;
+ std::string targetId = to_string(
+ static_cast(chip::app::Clusters::TargetNavigator::Structs::TargetInfo::Fields::kIdentifier));
+ std::string targetName = to_string(
+ static_cast(chip::app::Clusters::TargetNavigator::Structs::TargetInfo::Fields::kName));
+ for (Json::Value & entry : value[attrId])
+ {
+ if (!entry[targetId].isUInt() || !entry[targetName].isString() || entry[targetId].asUInt() > 255)
+ {
+ // invalid target ID. Ignore.
+ ChipLogError(Zcl, "TargetNavigatorManager::HandleNavigateTarget invalid target ignored");
+ i++;
+ continue;
+ }
+ Structs::TargetInfo::Type outputInfo;
+ outputInfo.identifier = static_cast(entry[targetId].asUInt());
+ outputInfo.name = CharSpan::fromCharString(entry[targetName].asCString());
+ ReturnErrorOnFailure(encoder.Encode(outputInfo));
+ i++;
+ }
+ return CHIP_NO_ERROR;
+ });
+ }
+ }
+ }
+ }
+
// NOTE: the ids for each target start at 1 so that we can reserve 0 as "no current target"
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
int i = 0;
@@ -46,6 +98,32 @@ CHIP_ERROR TargetNavigatorManager::HandleGetTargetList(AttributeValueEncoder & a
uint8_t TargetNavigatorManager::HandleGetCurrentTarget()
{
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleGetCurrentTarget");
+
+ if (mAttributeDelegate != nullptr)
+ {
+ chip::app::ConcreteReadAttributePath aPath(mEndpointId, chip::app::Clusters::TargetNavigator::Id,
+ chip::app::Clusters::TargetNavigator::Attributes::TargetList::Id);
+ const char * resStr = mAttributeDelegate->Read(aPath);
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleGetCurrentTarget response %s", resStr);
+
+ if (resStr != nullptr && *resStr != 0)
+ {
+ Json::Reader reader;
+ Json::Value value;
+ if (reader.parse(resStr, value))
+ {
+ std::string attrId = to_string(chip::app::Clusters::TargetNavigator::Attributes::CurrentTarget::Id);
+ ChipLogProgress(Zcl, "TargetNavigatorManager::HandleGetCurrentTarget response parsing done. reading attr %s",
+ attrId.c_str());
+ if (value[attrId].isUInt() && value[attrId].asUInt() < 256)
+ {
+ return static_cast(value[attrId].asUInt());
+ }
+ }
+ }
+ }
+
return mCurrentTarget;
}
diff --git a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h
index f4cb0b372bc7e3..8e0825c24c93ce 100644
--- a/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h
+++ b/examples/tv-app/android/include/target-navigator/TargetNavigatorManager.h
@@ -17,29 +17,38 @@
#pragma once
+#include "../../java/ContentAppAttributeDelegate.h"
#include
using chip::CharSpan;
+using chip::EndpointId;
using chip::app::AttributeValueEncoder;
using chip::app::CommandResponseHelper;
-using TargetNavigatorDelegate = chip::app::Clusters::TargetNavigator::Delegate;
-using TargetInfoType = chip::app::Clusters::TargetNavigator::Structs::TargetInfo::Type;
-using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type;
+using TargetNavigatorDelegate = chip::app::Clusters::TargetNavigator::Delegate;
+using TargetInfoType = chip::app::Clusters::TargetNavigator::Structs::TargetInfo::Type;
+using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type;
+using ContentAppAttributeDelegate = chip::AppPlatform::ContentAppAttributeDelegate;
class TargetNavigatorManager : public TargetNavigatorDelegate
{
public:
- TargetNavigatorManager() : TargetNavigatorManager({ "exampleName", "exampleName" }, kNoCurrentTarget){};
- TargetNavigatorManager(std::list targets, uint8_t currentTarget);
+ TargetNavigatorManager() : TargetNavigatorManager(nullptr, { "exampleName", "exampleName" }, kNoCurrentTarget){};
+ TargetNavigatorManager(ContentAppAttributeDelegate * attributeDelegate, std::list targets, uint8_t currentTarget);
CHIP_ERROR HandleGetTargetList(AttributeValueEncoder & aEncoder) override;
uint8_t HandleGetCurrentTarget() override;
void HandleNavigateTarget(CommandResponseHelper & responser, const uint64_t & target,
const CharSpan & data) override;
+ void SetEndpointId(EndpointId epId) { mEndpointId = epId; };
+
protected:
// NOTE: the ids for each target start at 1 so that we can reserve 0 as "no current target"
static const uint8_t kNoCurrentTarget = 0;
std::list mTargets;
uint8_t mCurrentTarget;
+
+ EndpointId mEndpointId;
+
+ ContentAppAttributeDelegate * mAttributeDelegate;
};
diff --git a/examples/tv-app/android/java/AppImpl.h b/examples/tv-app/android/java/AppImpl.h
index e86a750cc533c4..70ea36f452ac30 100644
--- a/examples/tv-app/android/java/AppImpl.h
+++ b/examples/tv-app/android/java/AppImpl.h
@@ -91,7 +91,8 @@ class DLL_EXPORT ContentAppImpl : public ContentApp
mAccountLoginDelegate(setupPIN), mContentLauncherDelegate(attributeDelegate, { "image/*", "video/*" },
to_underlying(SupportedStreamingProtocol::kDash) |
to_underlying(SupportedStreamingProtocol::kHls)),
- mMediaPlaybackDelegate(attributeDelegate), mTargetNavigatorDelegate({ "home", "search", "info", "guide", "menu" }, 0){};
+ mMediaPlaybackDelegate(attributeDelegate),
+ mTargetNavigatorDelegate(attributeDelegate, { "home", "search", "info", "guide", "menu" }, 0){};
virtual ~ContentAppImpl() {}
AccountLoginDelegate * GetAccountLoginDelegate() override { return &mAccountLoginDelegate; };
@@ -109,7 +110,11 @@ class DLL_EXPORT ContentAppImpl : public ContentApp
mMediaPlaybackDelegate.SetEndpointId(GetEndpointId());
return &mMediaPlaybackDelegate;
};
- TargetNavigatorDelegate * GetTargetNavigatorDelegate() override { return &mTargetNavigatorDelegate; };
+ TargetNavigatorDelegate * GetTargetNavigatorDelegate() override
+ {
+ mTargetNavigatorDelegate.SetEndpointId(GetEndpointId());
+ return &mTargetNavigatorDelegate;
+ };
protected:
ApplicationBasicManager mApplicationBasicDelegate;
diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
index c5b2e11c97e485..0b406ab4237794 100644
--- a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
+++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp
@@ -34,8 +34,13 @@
namespace chip {
namespace AppPlatform {
-using CommandHandlerInterface = chip::app::CommandHandlerInterface;
-using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LaunchResponse::Type;
+using CommandHandlerInterface = chip::app::CommandHandlerInterface;
+using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LaunchResponse::Type;
+using PlaybackResponseType = chip::app::Clusters::MediaPlayback::Commands::PlaybackResponse::Type;
+using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type;
+
+const std::string FAILURE_KEY = "PlatformError";
+const std::string FAILURE_STATUS_KEY = "Status";
void ContentAppCommandDelegate::InvokeCommand(CommandHandlerInterface::HandlerContext & handlerContext)
{
@@ -49,8 +54,9 @@ void ContentAppCommandDelegate::InvokeCommand(CommandHandlerInterface::HandlerCo
err = TlvToJson(readerForJson, json);
if (err != CHIP_NO_ERROR)
{
- // TODO : Add an interface to let the apps know a message came but there was a serialization error.
- handlerContext.SetCommandNotHandled();
+ handlerContext.SetCommandHandled();
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
+ Protocols::InteractionModel::Status::InvalidCommand);
return;
}
@@ -85,11 +91,25 @@ void ContentAppCommandDelegate::InvokeCommand(CommandHandlerInterface::HandlerCo
void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::HandlerContext & handlerContext, const char * response)
{
+ handlerContext.SetCommandHandled();
Json::Reader reader;
Json::Value value;
if (!reader.parse(response, value))
{
- handlerContext.SetCommandNotHandled();
+ return;
+ }
+
+ // handle errors from platform-app
+ if (!value[FAILURE_KEY].empty())
+ {
+ value = value[FAILURE_KEY];
+ if (!value[FAILURE_STATUS_KEY].empty() && value[FAILURE_STATUS_KEY].isUInt())
+ {
+ handlerContext.mCommandHandler.AddStatus(
+ handlerContext.mRequestPath, static_cast(value[FAILURE_STATUS_KEY].asUInt()));
+ return;
+ }
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Failure);
return;
}
@@ -97,28 +117,75 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand
{
case app::Clusters::ContentLauncher::Id: {
LaunchResponseType launchResponse;
- if (value["0"].empty())
+ std::string statusFieldId =
+ std::to_string(to_underlying(app::Clusters::ContentLauncher::Commands::LaunchResponse::Fields::kStatus));
+ if (value[statusFieldId].empty())
{
- launchResponse.status = chip::app::Clusters::ContentLauncher::ContentLaunchStatusEnum::kAuthFailed;
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Failure);
+ return;
}
else
{
- launchResponse.status = static_cast(value["0"].asInt());
- if (!value["1"].empty())
+ launchResponse.status =
+ static_cast(value[statusFieldId].asInt());
+ std::string dataFieldId =
+ std::to_string(to_underlying(app::Clusters::ContentLauncher::Commands::LaunchResponse::Fields::kData));
+ if (!value[dataFieldId].empty())
{
- launchResponse.data = chip::MakeOptional(CharSpan::fromCharString(value["1"].asCString()));
+ launchResponse.data = chip::MakeOptional(CharSpan::fromCharString(value[dataFieldId].asCString()));
}
}
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, launchResponse);
- handlerContext.SetCommandHandled();
break;
}
- // case app::Clusters::TargetNavigator::Id:
- // break;
+ case app::Clusters::TargetNavigator::Id: {
+ NavigateTargetResponseType navigateTargetResponse;
+ std::string statusFieldId =
+ std::to_string(to_underlying(app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Fields::kStatus));
+ if (value[statusFieldId].empty())
+ {
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Failure);
+ return;
+ }
+ else
+ {
+ navigateTargetResponse.status =
+ static_cast(value[statusFieldId].asInt());
+ std::string dataFieldId =
+ std::to_string(to_underlying(app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Fields::kData));
+ if (!value[dataFieldId].empty())
+ {
+ navigateTargetResponse.data = chip::MakeOptional(CharSpan::fromCharString(value[dataFieldId].asCString()));
+ }
+ }
+ handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, navigateTargetResponse);
+ break;
+ }
- // case app::Clusters::MediaPlayback::Id:
- // break;
+ case app::Clusters::MediaPlayback::Id: {
+ PlaybackResponseType playbackResponse;
+ std::string statusFieldId =
+ std::to_string(to_underlying(app::Clusters::MediaPlayback::Commands::PlaybackResponse::Fields::kStatus));
+ if (value[statusFieldId].empty())
+ {
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Failure);
+ return;
+ }
+ else
+ {
+ playbackResponse.status =
+ static_cast(value[statusFieldId].asInt());
+ std::string dataFieldId =
+ std::to_string(to_underlying(app::Clusters::MediaPlayback::Commands::PlaybackResponse::Fields::kData));
+ if (!value[dataFieldId].empty())
+ {
+ playbackResponse.data = chip::MakeOptional(CharSpan::fromCharString(value[dataFieldId].asCString()));
+ }
+ }
+ handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, playbackResponse);
+ break;
+ }
// case app::Clusters::AccountLogin::Id:
// break;