Skip to content

Commit 0a886da

Browse files
Android tv-casting-app: simplified command API
1 parent 30a7636 commit 0a886da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1933
-236
lines changed

examples/tv-casting-app/APIs.md

+105-5
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,8 @@ discover available endpoints supported by a Casting Player.
693693
### Connect to a Casting Player
694694

695695
_{Complete Connection examples: [Linux](linux/simple-app-helper.cpp) |
696-
[iOS](darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift)}_
696+
[Android](android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java)
697+
| [iOS](darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift)}_
697698

698699
Each `CastingPlayer` object created during
699700
[Discovery](#discover-casting-players) contains information such as
@@ -740,6 +741,40 @@ targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler,
740741
...
741742
```
742743
744+
On Android, the Casting Client may call `verifyOrEstablishConnection` on the
745+
`CastingPlayer` object it wants to connect to and accept or handle exceptions on
746+
the returned `CompletableFuture` object.
747+
748+
```java
749+
private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60;
750+
...
751+
...
752+
753+
EndpointFilter desiredEndpointFilter = new EndpointFilter(null, 65521, new ArrayList<DeviceTypeStruct>());
754+
755+
// The desired commissioning window timeout and EndpointFilter are optional.
756+
CompletableFuture<Void> completableFuture = targetCastingPlayer.verifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter);
757+
completableFuture
758+
.thenAccept(
759+
(response) -> {
760+
Log.i(TAG, "CompletableFuture.thenAccept(), connected to CastingPlayer with deviceId: " + targetCastingPlayer.getDeviceId());
761+
762+
getActivity().runOnUiThread(() -> {
763+
connectionFragmentNextButton.setEnabled(true);
764+
});
765+
}
766+
).exceptionally(
767+
exc -> {
768+
Log.e(TAG, "CompletableFuture.exceptionally(), CastingPLayer connection failed: " + exc.getMessage());
769+
770+
getActivity().runOnUiThread(() -> {
771+
connectionFragmentStatusTextView.setText("Casting Player connection failed due to: " + exc.getMessage());
772+
});
773+
return null;
774+
}
775+
);
776+
```
777+
743778
On iOS, the Casting Client may call `verifyOrEstablishConnection` on the
744779
`MCCastingPlayer` object it wants to connect to and handle any `NSErrors` that
745780
may happen in the process.
@@ -774,6 +809,8 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) {
774809
### Select an Endpoint on the Casting Player
775810
776811
_{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp) |
812+
[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java)
813+
|
777814
[iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_
778815
779816
On a successful connection with a `CastingPlayer`, a Casting Client may select
@@ -800,6 +837,29 @@ if (it != endpoints.end())
800837
}
801838
```
802839
840+
On Android, it can select an `Endpoint` as shown below.
841+
842+
```java
843+
private static final Integer SAMPLE_ENDPOINT_VID = 65521;
844+
...
845+
List<Endpoint> endpoints = selectedCastingPlayer.getEndpoints();
846+
if (endpoints == null) {
847+
Log.e(TAG, "No Endpoints found on CastingPlayer");
848+
return;
849+
}
850+
851+
Endpoint endpoint = endpoints
852+
.stream()
853+
.filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId()))
854+
.findFirst()
855+
.get();
856+
857+
if (endpoint == null) {
858+
Log.e(TAG, "No Endpoint with chosen vendorID: " + SAMPLE_ENDPOINT_VID + " found on CastingPlayer");
859+
return;
860+
}
861+
```
862+
803863
On iOS, it can select an `MCEndpoint` similarly and as shown below.
804864
805865
```swift
@@ -834,6 +894,8 @@ response types to use with these APIs:
834894
### Issuing Commands
835895
836896
_{Complete Command invocation examples: [Linux](linux/simple-app-helper.cpp) |
897+
[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java)
898+
|
837899
[iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_
838900
839901
The Casting Client can get a reference to an `Endpoint` on a `CastingPlayer`,
@@ -880,6 +942,46 @@ void InvokeContentLauncherLaunchURL(matter::casting::memory::Strong<matter::cast
880942
}
881943
```
882944
945+
On Android, given an `Endpoint` endpoint, it can send a `LaunchURL` command
946+
(part of the Content Launcher cluster) by calling the `invoke` API on a
947+
`ContentLauncherClusterLaunchURLCommand`
948+
949+
```java
950+
if (!endpoint.hasCluster(MatterClusters.ContentLauncherCluster.class)) {
951+
Log.e(TAG, "Endpoint with chosen vendorID does not support ContentLauncher cluster");
952+
return;
953+
}
954+
955+
MatterClusters.ContentLauncherCluster cluster = endpoint.getCluster(MatterClusters.ContentLauncherCluster.class);
956+
MatterCommands.ContentLauncherClusterLaunchURLCommand command = cluster.getCommand(MatterCommands.ContentLauncherClusterLaunchURLCommand.class);
957+
if (command == null) {
958+
Log.e(TAG, "ContentLauncher cluster on Endpoint with chosen vendorID does not support LaunchURL command");
959+
return;
960+
}
961+
962+
// create the request object from GUI inputs
963+
MatterCommands.ContentLauncherClusterLaunchURLRequest request = new MatterCommands.ContentLauncherClusterLaunchURLRequest();
964+
request.contentURL = ((EditText) getView().findViewById(R.id.contentUrlEditText)).getText().toString();
965+
request.displayString = ((EditText) getView().findViewById(R.id.contentDisplayStringEditText)).getText().toString();
966+
CompletableFuture<MatterCommands.ContentLauncherClusterResponse> responseFuture = command.invoke(request, 5000);
967+
responseFuture
968+
.thenAccept(
969+
response -> {
970+
Log.d(TAG, "Command response " + response);
971+
TextView launchUrlStatus = getView().findViewById(R.id.launchUrlStatus);
972+
getActivity().runOnUiThread(() ->
973+
launchUrlStatus.setText("Success! Response data: " + response.data));
974+
}
975+
).exceptionally(
976+
exc -> {
977+
Log.e(TAG, "Command failure: " + exc.getMessage());
978+
TextView launchUrlStatus = getView().findViewById(R.id.launchUrlStatus);
979+
getActivity().runOnUiThread(() -> launchUrlStatus.setText("Command failure: " + exc.getMessage()));
980+
return null;
981+
}
982+
);
983+
```
984+
883985
On iOS, given an `MCEndpoint` endpoint, it can send a `LaunchURL` command (part
884986
of the Content Launcher cluster) by calling the `invoke` API on a
885987
`MCContentLauncherClusterLaunchURLCommand`
@@ -1042,10 +1144,8 @@ vendorIDAttribute!.read(nil) { context, before, after, err in
10421144
10431145
### Subscriptions
10441146
1045-
_{Complete Attribute subscription examples:
1046-
[Linux](linux/simple-app-helper.cpp)}_
1047-
1048-
_{Complete Attribute Read examples: [Linux](linux/simple-app-helper.cpp) |
1147+
_{Complete Attribute subscription examples: [Linux](linux/simple-app-helper.cpp)
1148+
|
10491149
[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_
10501150
10511151
A Casting Client may subscribe to an attribute on an `Endpoint` of the

examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import com.chip.casting.TvCastingApp;
1212
import com.chip.casting.util.GlobalCastingConstants;
1313
import com.chip.casting.util.PreferencesConfigurationManager;
14+
import com.matter.casting.ActionSelectorFragment;
1415
import com.matter.casting.ConnectionExampleFragment;
16+
import com.matter.casting.ContentLauncherLaunchURLExampleFragment;
1517
import com.matter.casting.DiscoveryExampleFragment;
1618
import com.matter.casting.InitializationExample;
1719
import com.matter.casting.core.CastingPlayer;
@@ -22,7 +24,8 @@ public class MainActivity extends AppCompatActivity
2224
ConnectionFragment.Callback,
2325
SelectClusterFragment.Callback,
2426
DiscoveryExampleFragment.Callback,
25-
ConnectionExampleFragment.Callback {
27+
ConnectionExampleFragment.Callback,
28+
ActionSelectorFragment.Callback {
2629

2730
private static final String TAG = MainActivity.class.getSimpleName();
2831

@@ -73,9 +76,12 @@ public void handleCommissioningComplete() {
7376
@Override
7477
public void handleConnectionComplete(CastingPlayer castingPlayer) {
7578
Log.i(TAG, "MainActivity.handleConnectionComplete() called ");
79+
showFragment(ActionSelectorFragment.newInstance(castingPlayer));
80+
}
7681

77-
// TODO: Implement in following PRs. Select Cluster Fragment.
78-
// showFragment(SelectClusterFragment.newInstance(tvCastingApp));
82+
@Override
83+
public void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer) {
84+
showFragment(ContentLauncherLaunchURLExampleFragment.newInstance(selectedCastingPlayer));
7985
}
8086

8187
@Override
@@ -95,7 +101,10 @@ public void handleMediaPlaybackSelected() {
95101

96102
@Override
97103
public void handleDisconnect() {
98-
showFragment(CommissionerDiscoveryFragment.newInstance(tvCastingApp));
104+
showFragment(
105+
GlobalCastingConstants.ChipCastingSimplified
106+
? DiscoveryExampleFragment.newInstance()
107+
: CommissionerDiscoveryFragment.newInstance(tvCastingApp));
99108
}
100109

101110
/**

examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ public class GlobalCastingConstants {
66
public static final int SetupPasscode = 20202021;
77
public static final int Discriminator = 0xF00;
88
public static final boolean ChipCastingSimplified =
9-
false; // set this flag to true to demo simplified casting APIs
9+
true; // set this flag to true to demo simplified casting APIs
1010
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.matter.casting;
18+
19+
import android.os.Bundle;
20+
import android.util.Log;
21+
import android.view.LayoutInflater;
22+
import android.view.View;
23+
import android.view.ViewGroup;
24+
import androidx.annotation.Nullable;
25+
import androidx.fragment.app.Fragment;
26+
import com.R;
27+
import com.matter.casting.core.CastingPlayer;
28+
29+
/** An interstitial {@link Fragment} to select one of the supported media actions to perform */
30+
public class ActionSelectorFragment extends Fragment {
31+
private static final String TAG = ActionSelectorFragment.class.getSimpleName();
32+
33+
private final CastingPlayer selectedCastingPlayer;
34+
35+
private View.OnClickListener selectContentLauncherButtonClickListener;
36+
private View.OnClickListener disconnectButtonClickListener;
37+
38+
public ActionSelectorFragment(CastingPlayer selectedCastingPlayer) {
39+
this.selectedCastingPlayer = selectedCastingPlayer;
40+
}
41+
42+
/**
43+
* Use this factory method to create a new instance of this fragment using the provided
44+
* parameters.
45+
*
46+
* @param selectedCastingPlayer CastingPlayer that the casting app connected to
47+
* @return A new instance of fragment SelectActionFragment.
48+
*/
49+
public static ActionSelectorFragment newInstance(CastingPlayer selectedCastingPlayer) {
50+
return new ActionSelectorFragment(selectedCastingPlayer);
51+
}
52+
53+
@Override
54+
public void onCreate(Bundle savedInstanceState) {
55+
super.onCreate(savedInstanceState);
56+
}
57+
58+
@Override
59+
public View onCreateView(
60+
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
61+
ActionSelectorFragment.Callback callback = (ActionSelectorFragment.Callback) this.getActivity();
62+
this.selectContentLauncherButtonClickListener =
63+
v -> {
64+
Log.d(TAG, "handle() called on selectContentLauncherButtonClickListener");
65+
callback.handleContentLauncherLaunchURLSelected(selectedCastingPlayer);
66+
};
67+
68+
this.disconnectButtonClickListener =
69+
v -> {
70+
Log.d(TAG, "Disconnecting from current casting player");
71+
selectedCastingPlayer.disconnect();
72+
callback.handleDisconnect();
73+
};
74+
75+
return inflater.inflate(R.layout.fragment_matter_action_selector, container, false);
76+
}
77+
78+
@Override
79+
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
80+
super.onViewCreated(view, savedInstanceState);
81+
Log.d(TAG, "ActionSelectorFragment.onViewCreated called");
82+
getView()
83+
.findViewById(R.id.selectContentLauncherLaunchURLButton)
84+
.setOnClickListener(selectContentLauncherButtonClickListener);
85+
86+
getView().findViewById(R.id.disconnectButton).setOnClickListener(disconnectButtonClickListener);
87+
}
88+
89+
/** Interface for notifying the host. */
90+
public interface Callback {
91+
/** Notifies listener to trigger transition on selection of Content Launcher cluster */
92+
void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer);
93+
94+
/** Notifies listener to trigger transition on click of the Disconnect button */
95+
void handleDisconnect();
96+
}
97+
}

examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,26 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
100100
new EndpointFilter(null, 65521, new ArrayList<DeviceTypeStruct>());
101101
// The desired commissioning window timeout and EndpointFilter are optional.
102102
CompletableFuture<Void> completableFuture =
103-
targetCastingPlayer.VerifyOrEstablishConnection(
103+
targetCastingPlayer.verifyOrEstablishConnection(
104104
MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter);
105105

106106
Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called");
107107

108+
Log.d(
109+
TAG,
110+
"onViewCreated() verifyOrEstablishConnection() completableFuture == null? "
111+
+ (completableFuture == null));
112+
108113
completableFuture
109-
.thenRun(
110-
() -> {
114+
.thenAccept(
115+
(response) -> {
111116
Log.i(
112117
TAG,
113-
"CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: "
118+
"CompletableFuture.thenAccept(), connected to CastingPlayer with deviceId: "
114119
+ targetCastingPlayer.getDeviceId());
115120
getActivity()
116121
.runOnUiThread(
117122
() -> {
118-
connectionFragmentStatusTextView.setText(
119-
"Connected to Casting Player with device name: "
120-
+ targetCastingPlayer.getDeviceName());
121123
connectionFragmentNextButton.setEnabled(true);
122124
});
123125
})

0 commit comments

Comments
 (0)