From ca9a840ade069d723c27e62dde9751bdd7352d4a Mon Sep 17 00:00:00 2001 From: amitnj <74272437+amitnj@users.noreply.github.com> Date: Tue, 20 Sep 2022 10:34:38 -0700 Subject: [PATCH 1/2] Support for subscriptions of attributes on content app endpoints. (#22741) * Support for subscriptions of attributes on content app endpoints. Make media playback cluster work for content apps. * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * Restyled by google-java-format Co-authored-by: Restyled.io --- .../matter/tv/app/api/IMatterAppAgent.aidl | 3 +- .../app/api/ReportAttributeChangeRequest.aidl | 12 - .../java/com/matter/tv/app/api/Clusters.java | 28 +++ .../example/contentapp/AttributeHolder.java | 30 +++ .../com/example/contentapp/MainActivity.java | 104 ++++++++- .../contentapp/matter/MatterAgentClient.java | 18 +- .../receiver/MatterCommandReceiver.java | 37 +-- .../src/main/res/layout/activity_main.xml | 43 +++- .../server/MatterCommissioningPrompter.java | 24 +- .../server/fragments/ContentAppFragment.java | 2 +- .../matter/tv/server/model/ContentApp.java | 10 +- .../tv/server/service/AppPlatformService.java | 4 + .../service/ContentAppAgentService.java | 41 +++- .../tv/server/service/MatterServant.java | 6 +- .../src/main/res/layout/applist_item.xml | 7 +- examples/tv-app/android/BUILD.gn | 2 + .../AppContentLauncherManager.cpp | 6 +- .../AppContentLauncherManager.h | 4 +- .../AppMediaPlaybackManager.cpp | 221 ++++++++++++++++++ .../media-playback/AppMediaPlaybackManager.h | 101 ++++++++ examples/tv-app/android/java/AppImpl.cpp | 25 +- examples/tv-app/android/java/AppImpl.h | 23 +- .../tv-app/android/java/AppPlatform-JNI.cpp | 10 +- .../matter/tv/server/tvapp/AppPlatform.java | 3 + 24 files changed, 684 insertions(+), 80 deletions(-) delete mode 100644 examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl create mode 100644 examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java create mode 100644 examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/AttributeHolder.java create mode 100644 examples/tv-app/android/include/media-playback/AppMediaPlaybackManager.cpp create mode 100644 examples/tv-app/android/include/media-playback/AppMediaPlaybackManager.h diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl index 930a2436fa1a2b..c7dd63667c0a71 100644 --- a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl +++ b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/IMatterAppAgent.aidl @@ -2,7 +2,6 @@ package com.matter.tv.app.api; import com.matter.tv.app.api.SetSupportedClustersRequest; -import com.matter.tv.app.api.ReportAttributeChangeRequest; /* * To use this interface, partners should query for and bind to a service that handles the "com.matter.tv.app.api.action.MatterAppAgent" Action. @@ -29,5 +28,5 @@ interface IMatterAppAgent { * @return - ReportAttributeChangeResult, returns success or error code */ // TODO : replace the boolean with some kind of enumerated status field - boolean reportAttributeChange(in ReportAttributeChangeRequest request); + boolean reportAttributeChange(in int clusterId, in int attributeId); } \ No newline at end of file diff --git a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl b/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl deleted file mode 100644 index abc10066340e44..00000000000000 --- a/examples/tv-app/android/App/common-api/src/main/aidl/com/matter/tv/app/api/ReportAttributeChangeRequest.aidl +++ /dev/null @@ -1,12 +0,0 @@ -// ReportAttributeChangeRequest.aidl -package com.matter.tv.app.api; - -parcelable ReportAttributeChangeRequest{ - - int clusterIdentifier; - - int attributeIdentifier; - - String value; - -} \ 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 new file mode 100644 index 00000000000000..dbb80b1e884933 --- /dev/null +++ b/examples/tv-app/android/App/common-api/src/main/java/com/matter/tv/app/api/Clusters.java @@ -0,0 +1,28 @@ +package com.matter.tv.app.api; + +public class Clusters { + // Clusters + public static class MediaPlayback { + public static final int Id = 1286; + + public static class Attributes { + public static final int CurrentState = 0; + } + + 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 ContentLauncher { + public static final int Id = 1290; + + public static class Attributes { + public static final int AcceptHeader = 0; + public static final int SupportedStreamingProtocols = 1; + } + } +} diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/AttributeHolder.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/AttributeHolder.java new file mode 100644 index 00000000000000..8e4678b9c073c5 --- /dev/null +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/AttributeHolder.java @@ -0,0 +1,30 @@ +package com.example.contentapp; + +import java.util.HashMap; +import java.util.Map; + +/** Class to hold attribute values to help test attribute read and subscribe use cases. */ +public class AttributeHolder { + private static AttributeHolder instance = new AttributeHolder(); + private Map> attributeValues = new HashMap<>(); + + private AttributeHolder() {}; + + public static AttributeHolder getInstance() { + return instance; + } + + public void setAttributeValue(long clusterId, long attributeId, Object value) { + Map attributes = attributeValues.get(clusterId); + if (attributes == null) { + attributes = new HashMap<>(); + attributeValues.put(clusterId, attributes); + } + attributes.put(attributeId, value); + } + + public Object getAttributeValue(long clusterId, long attributeId) { + Map attributes = attributeValues.get(clusterId); + return attributes.get(attributeId); + } +} 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 dc9b8eb26f6641..c346a5c1ea4b82 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 @@ -1,21 +1,121 @@ package com.example.contentapp; +import android.content.Intent; import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.Spinner; +import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.example.contentapp.matter.MatterAgentClient; +import com.matter.tv.app.api.Clusters; +import com.matter.tv.app.api.MatterIntentConstants; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { private static final String TAG = "ContentAppMainActivity"; + private static final String ATTR_PS_PLAYING = "Playback State : PLAYING"; + private static final String ATTR_PS_PAUSED = "Playback State : PAUSED"; + private static final String ATTR_PS_NOT_PLAYING = "Playback State : NOT_PLAYING"; + private static final String ATTR_PS_BUFFERING = "Playback State : BUFFERING"; + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + MatterAgentClient.initialize(getApplicationContext()); + setContentView(R.layout.activity_main); - MatterAgentClient matterAgentClient = MatterAgentClient.getInstance(getApplicationContext()); - final ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Intent intent = getIntent(); + String command = intent.getStringExtra(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD); + + // use the text in a TextView + TextView textView = (TextView) findViewById(R.id.commandTextView); + textView.setText("Command Payload : " + command); + + Button sendMessageButton = findViewById(R.id.sendMessageButton); + + sendMessageButton.setOnClickListener( + view -> { + Spinner dropdown = findViewById(R.id.spinnerAttribute); + String attribute = (String) dropdown.getSelectedItem(); + switch (attribute) { + case ATTR_PS_PLAYING: + AttributeHolder.getInstance() + .setAttributeValue( + Clusters.MediaPlayback.Id, + Clusters.MediaPlayback.Attributes.CurrentState, + Clusters.MediaPlayback.PlaybackStateEnum.Playing); + reportAttributeChange( + Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState); + break; + case ATTR_PS_PAUSED: + AttributeHolder.getInstance() + .setAttributeValue( + Clusters.MediaPlayback.Id, + Clusters.MediaPlayback.Attributes.CurrentState, + Clusters.MediaPlayback.PlaybackStateEnum.Paused); + reportAttributeChange( + Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState); + break; + case ATTR_PS_BUFFERING: + AttributeHolder.getInstance() + .setAttributeValue( + Clusters.MediaPlayback.Id, + Clusters.MediaPlayback.Attributes.CurrentState, + Clusters.MediaPlayback.PlaybackStateEnum.Buffering); + reportAttributeChange( + Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState); + break; + case ATTR_PS_NOT_PLAYING: + AttributeHolder.getInstance() + .setAttributeValue( + Clusters.MediaPlayback.Id, + Clusters.MediaPlayback.Attributes.CurrentState, + Clusters.MediaPlayback.PlaybackStateEnum.NotPlaying); + reportAttributeChange( + Clusters.MediaPlayback.Id, Clusters.MediaPlayback.Attributes.CurrentState); + break; + } + }); + Spinner dropdown = findViewById(R.id.spinnerAttribute); + String[] items = + new String[] {ATTR_PS_PLAYING, ATTR_PS_PAUSED, ATTR_PS_NOT_PLAYING, ATTR_PS_BUFFERING}; + ArrayAdapter adapter = + new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, items); + dropdown.setAdapter(adapter); + + 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) { + executorService.execute( + new Runnable() { + @Override + public void run() { + MatterAgentClient client = MatterAgentClient.getInstance(); + client.reportAttributeChange(clusterId, attributeId); + } + }); } } diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java index 36bb872c8854e3..79287e7ca08194 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/matter/MatterAgentClient.java @@ -29,7 +29,7 @@ public class MatterAgentClient { // TODO : Introduce dependency injection private MatterAgentClient() {}; - public static synchronized MatterAgentClient getInstance(Context context) { + public static synchronized void initialize(Context context) { if (instance == null || (instance.service == null && !instance.bound)) { instance = new MatterAgentClient(); if (!instance.bindService(context)) { @@ -39,6 +39,9 @@ public static synchronized MatterAgentClient getInstance(Context context) { Log.d(TAG, "Matter agent binding request successful."); } } + } + + public static MatterAgentClient getInstance() { return instance; } @@ -62,6 +65,19 @@ public void reportClusters() { } } + public void reportAttributeChange(int clusterId, int attributeId) { + IMatterAppAgent matterAgent = instance.getMatterAgent(); + if (matterAgent == null) { + Log.e(TAG, "Matter agent not retrieved."); + return; + } + try { + matterAgent.reportAttributeChange(clusterId, attributeId); + } catch (RemoteException e) { + Log.e(TAG, "Error invoking remote method to report attribute change to Matter agent"); + } + } + private IMatterAppAgent getMatterAgent() { try { latch.await(); 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 fc2e1271e61ad6..930dc63d26f43a 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 @@ -5,12 +5,12 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +import com.example.contentapp.AttributeHolder; +import com.example.contentapp.MainActivity; import com.matter.tv.app.api.MatterIntentConstants; public class MatterCommandReceiver extends BroadcastReceiver { private static final String TAG = "MatterCommandReceiver"; - private static final int ACCEPT_HEADER = 0; - private static final int SUPPORTED_STREAMING_PROTOCOLS = 1; @Override public void onReceive(Context context, Intent intent) { @@ -24,36 +24,41 @@ public void onReceive(Context context, Intent intent) { switch (intentAction) { case MatterIntentConstants.ACTION_MATTER_COMMAND: int commandId = intent.getIntExtra(MatterIntentConstants.EXTRA_COMMAND_ID, -1); + int clusterId = intent.getIntExtra(MatterIntentConstants.EXTRA_CLUSTER_ID, -1); if (commandId != -1) { - int clusterId = intent.getIntExtra(MatterIntentConstants.EXTRA_CLUSTER_ID, -1); byte[] commandPayload = intent.getByteArrayExtra(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD); - Log.d( - TAG, + String command = new String(commandPayload); + String message = new StringBuilder() .append("Received matter command ") .append(commandId) .append(" on cluster ") .append(clusterId) .append(" with payload : ") - .append(new String(commandPayload)) - .toString()); + .append(command) + .toString(); + Log.d(TAG, message); String response = "{\"0\":1, \"1\":\"custom response from content app\"}"; + + Intent in = new Intent(context, MainActivity.class); + in.putExtra(MatterIntentConstants.EXTRA_COMMAND_PAYLOAD, command); + context.startActivity(in); + + Log.d(TAG, "Started activity. Now sending response"); + sendResponseViaPendingIntent(context, intent, response); } else { int attributeId = intent.getIntExtra(MatterIntentConstants.EXTRA_ATTRIBUTE_ID, -1); String attributeAction = intent.getStringExtra(MatterIntentConstants.EXTRA_ATTRIBUTE_ACTION); if (attributeAction.equals(MatterIntentConstants.ATTRIBUTE_ACTION_READ)) { - String response; - if (attributeId == ACCEPT_HEADER) { - response = - "{\"0\": [\"video/mp4\", \"application/x-mpegURL\", \"application/dash+xml\"] }"; - } else if (attributeId == SUPPORTED_STREAMING_PROTOCOLS) { - response = "{\"1\":3}"; - } else { - response = ""; - } + String response = + "{\"" + + attributeId + + "\":" + + AttributeHolder.getInstance().getAttributeValue(clusterId, attributeId) + + "}"; sendResponseViaPendingIntent(context, intent, response); } else { Log.e( diff --git a/examples/tv-app/android/App/content-app/src/main/res/layout/activity_main.xml b/examples/tv-app/android/App/content-app/src/main/res/layout/activity_main.xml index 4fc244418b5fe5..dadcd97935481a 100644 --- a/examples/tv-app/android/App/content-app/src/main/res/layout/activity_main.xml +++ b/examples/tv-app/android/App/content-app/src/main/res/layout/activity_main.xml @@ -7,12 +7,49 @@ tools:context=".MainActivity"> + + + + + +