Skip to content

Commit e03a83c

Browse files
tv-casting-app: simplified android connection API (#31617)
1 parent 93c5213 commit e03a83c

16 files changed

+584
-37
lines changed

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

+14-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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.ConnectionExampleFragment;
1415
import com.matter.casting.DiscoveryExampleFragment;
1516
import com.matter.casting.InitializationExample;
1617
import com.matter.casting.core.CastingPlayer;
@@ -20,7 +21,8 @@ public class MainActivity extends AppCompatActivity
2021
implements CommissionerDiscoveryFragment.Callback,
2122
ConnectionFragment.Callback,
2223
SelectClusterFragment.Callback,
23-
DiscoveryExampleFragment.Callback {
24+
DiscoveryExampleFragment.Callback,
25+
ConnectionExampleFragment.Callback {
2426

2527
private static final String TAG = MainActivity.class.getSimpleName();
2628

@@ -58,17 +60,24 @@ public void handleCommissioningButtonClicked(DiscoveredNodeData commissioner) {
5860
}
5961

6062
@Override
61-
public void handleConnectionButtonClicked(CastingPlayer player) {
63+
public void handleConnectionButtonClicked(CastingPlayer castingPlayer) {
6264
Log.i(TAG, "MainActivity.handleConnectionButtonClicked() called");
63-
// TODO: In future PR, show fragment that connects to the player.
64-
// showFragment(ConnectionFragment.newInstance(CastingPlayer player));
65+
showFragment(ConnectionExampleFragment.newInstance(castingPlayer));
6566
}
6667

6768
@Override
6869
public void handleCommissioningComplete() {
6970
showFragment(SelectClusterFragment.newInstance(tvCastingApp));
7071
}
7172

73+
@Override
74+
public void handleConnectionComplete(CastingPlayer castingPlayer) {
75+
Log.i(TAG, "MainActivity.handleConnectionComplete() called ");
76+
77+
// TODO: Implement in following PRs. Select Cluster Fragment.
78+
// showFragment(SelectClusterFragment.newInstance(tvCastingApp));
79+
}
80+
7281
@Override
7382
public void handleContentLauncherSelected() {
7483
showFragment(ContentLauncherFragment.newInstance(tvCastingApp));
@@ -115,7 +124,7 @@ private boolean initJni() {
115124
private void showFragment(Fragment fragment, boolean showOnBack) {
116125
Log.d(
117126
TAG,
118-
"showFragment called with " + fragment.getClass().getSimpleName() + " and " + showOnBack);
127+
"showFragment() called with " + fragment.getClass().getSimpleName() + " and " + showOnBack);
119128
FragmentTransaction fragmentTransaction =
120129
getSupportFragmentManager()
121130
.beginTransaction()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 android.widget.Button;
25+
import android.widget.TextView;
26+
import androidx.annotation.Nullable;
27+
import androidx.fragment.app.Fragment;
28+
import com.R;
29+
import com.matter.casting.core.CastingPlayer;
30+
import com.matter.casting.support.DeviceTypeStruct;
31+
import com.matter.casting.support.EndpointFilter;
32+
import java.util.ArrayList;
33+
import java.util.concurrent.CompletableFuture;
34+
import java.util.concurrent.Executors;
35+
36+
/** A {@link Fragment} to Verify or establish a connection with a selected Casting Player. */
37+
public class ConnectionExampleFragment extends Fragment {
38+
private static final String TAG = ConnectionExampleFragment.class.getSimpleName();
39+
// Time (in sec) to keep the commissioning window open, if commissioning is required.
40+
// Must be >= 3 minutes.
41+
private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60;
42+
private final CastingPlayer targetCastingPlayer;
43+
private TextView connectionFragmentStatusTextView;
44+
private Button connectionFragmentNextButton;
45+
46+
public ConnectionExampleFragment(CastingPlayer targetCastingPlayer) {
47+
Log.i(TAG, "ConnectionExampleFragment() called with target CastingPlayer");
48+
this.targetCastingPlayer = targetCastingPlayer;
49+
}
50+
51+
/**
52+
* Use this factory method to create a new instance of this fragment using the provided
53+
* parameters.
54+
*
55+
* @return A new instance of fragment ConnectionExampleFragment.
56+
*/
57+
public static ConnectionExampleFragment newInstance(CastingPlayer castingPlayer) {
58+
Log.i(TAG, "newInstance() called");
59+
return new ConnectionExampleFragment(castingPlayer);
60+
}
61+
62+
@Override
63+
public void onCreate(Bundle savedInstanceState) {
64+
super.onCreate(savedInstanceState);
65+
Log.i(TAG, "onCreate() called");
66+
}
67+
68+
@Override
69+
public View onCreateView(
70+
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
71+
Log.i(TAG, "onCreateView() called");
72+
// Inflate the layout for this fragment
73+
return inflater.inflate(R.layout.fragment_matter_connection_example, container, false);
74+
}
75+
76+
@Override
77+
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
78+
super.onViewCreated(view, savedInstanceState);
79+
Log.i(TAG, "onViewCreated() called");
80+
81+
connectionFragmentStatusTextView = getView().findViewById(R.id.connectionFragmentStatusText);
82+
connectionFragmentStatusTextView.setText(
83+
"Verifying or establishing connection with Casting Player with device name: "
84+
+ targetCastingPlayer.getDeviceName());
85+
86+
connectionFragmentNextButton = getView().findViewById(R.id.connectionFragmentNextButton);
87+
Callback callback = (ConnectionExampleFragment.Callback) this.getActivity();
88+
connectionFragmentNextButton.setOnClickListener(
89+
v -> {
90+
Log.i(TAG, "onViewCreated() NEXT clicked. Calling handleConnectionComplete()");
91+
callback.handleConnectionComplete(targetCastingPlayer);
92+
});
93+
94+
Executors.newSingleThreadExecutor()
95+
.submit(
96+
() -> {
97+
Log.d(TAG, "onViewCreated() calling verifyOrEstablishConnection()");
98+
99+
EndpointFilter desiredEndpointFilter =
100+
new EndpointFilter(null, 65521, new ArrayList<DeviceTypeStruct>());
101+
// The desired commissioning window timeout and EndpointFilter are optional.
102+
CompletableFuture<Void> completableFuture =
103+
targetCastingPlayer.VerifyOrEstablishConnection(
104+
MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter);
105+
106+
Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called");
107+
108+
completableFuture
109+
.thenRun(
110+
() -> {
111+
Log.i(
112+
TAG,
113+
"CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: "
114+
+ targetCastingPlayer.getDeviceId());
115+
getActivity()
116+
.runOnUiThread(
117+
() -> {
118+
connectionFragmentStatusTextView.setText(
119+
"Connected to Casting Player with device name: "
120+
+ targetCastingPlayer.getDeviceName());
121+
connectionFragmentNextButton.setEnabled(true);
122+
});
123+
})
124+
.exceptionally(
125+
exc -> {
126+
Log.e(
127+
TAG,
128+
"CompletableFuture.exceptionally(), CastingPLayer connection failed: "
129+
+ exc.getMessage());
130+
getActivity()
131+
.runOnUiThread(
132+
() -> {
133+
connectionFragmentStatusTextView.setText(
134+
"Casting Player connection failed due to: "
135+
+ exc.getMessage());
136+
});
137+
return null;
138+
});
139+
});
140+
}
141+
142+
/** Interface for notifying the host. */
143+
public interface Callback {
144+
/** Notifies listener to trigger transition on completion of connection */
145+
void handleConnectionComplete(CastingPlayer castingPlayer);
146+
}
147+
}

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,10 @@ public View getView(int i, View view, ViewGroup viewGroup) {
338338
CastingPlayer castingPlayer = playerList.get(i);
339339
Log.d(
340340
TAG,
341-
"OnItemClickListener.onClick() called for castingPlayer with deviceId: "
341+
"OnClickListener.onClick() called for CastingPlayer with deviceId: "
342342
+ castingPlayer.getDeviceId());
343343
DiscoveryExampleFragment.Callback callback1 = (DiscoveryExampleFragment.Callback) context;
344-
// TODO: In following PRs. Implement CastingPlayer connection
345-
// callback1.handleCommissioningButtonClicked(castingPlayer);
344+
callback1.handleConnectionButtonClicked(castingPlayer);
346345
};
347346
playerDescription.setOnClickListener(clickListener);
348347
return view;

examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java

+36-20
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
*/
1717
package com.matter.casting.core;
1818

19+
import com.matter.casting.support.EndpointFilter;
1920
import java.net.InetAddress;
2021
import java.util.List;
22+
import java.util.concurrent.CompletableFuture;
2123

2224
/**
2325
* The CastingPlayer interface defines a Matter commissioner that is able to play media to a
@@ -55,24 +57,38 @@ public interface CastingPlayer {
5557
@Override
5658
int hashCode();
5759

58-
// TODO: Implement in following PRs. Related to player connection implementation.
59-
// List<Endpoint> getEndpoints();
60-
//
61-
// ConnectionState getConnectionState();
62-
//
63-
// CompletableFuture<Void> connect(long timeout);
64-
//
65-
// static class ConnectionState extends Observable {
66-
// private boolean connected;
67-
//
68-
// void setConnected(boolean connected) {
69-
// this.connected = connected;
70-
// setChanged();
71-
// notifyObservers(this.connected);
72-
// }
73-
//
74-
// boolean isConnected() {
75-
// return connected;
76-
// }
77-
// }
60+
/**
61+
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
62+
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
63+
* disk, this will execute the user directed commissioning process.
64+
*
65+
* @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window
66+
* open, if commissioning is required. Needs to be >= MIN_CONNECTION_TIMEOUT_SEC.
67+
* @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint
68+
* that the client wants to interact with after commissioning. If this value is passed in, the
69+
* VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired
70+
* Endpoint is not found in the on device CastingStore.
71+
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
72+
* The CompletableFuture will be completed with a Void value if the
73+
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
74+
* completed with an Exception. The Exception will be of type
75+
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
76+
* CastingException will contain the error code and message from the CastingApp.
77+
*/
78+
CompletableFuture<Void> VerifyOrEstablishConnection(
79+
long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter);
80+
81+
/**
82+
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
83+
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
84+
* disk, this will execute the user directed commissioning process.
85+
*
86+
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
87+
* The CompletableFuture will be completed with a Void value if the
88+
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
89+
* completed with an Exception. The Exception will be of type
90+
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
91+
* CastingException will contain the error code and message from the CastingApp.
92+
*/
93+
CompletableFuture<Void> VerifyOrEstablishConnection();
7894
}

examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java

+49
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
*/
1717
package com.matter.casting.core;
1818

19+
import com.matter.casting.support.EndpointFilter;
1920
import java.net.InetAddress;
2021
import java.util.List;
2122
import java.util.Objects;
23+
import java.util.concurrent.CompletableFuture;
2224

2325
/**
2426
* A Matter Casting Player represents a Matter commissioner that is able to play media to a physical
@@ -27,6 +29,13 @@
2729
* the service discovered/resolved.
2830
*/
2931
public class MatterCastingPlayer implements CastingPlayer {
32+
private static final String TAG = MatterCastingPlayer.class.getSimpleName();
33+
/**
34+
* Time (in sec) to keep the commissioning window open, if commissioning is required. Must be >= 3
35+
* minutes.
36+
*/
37+
public static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60;
38+
3039
private boolean connected;
3140
private String deviceId;
3241
private String deviceName;
@@ -37,6 +46,7 @@ public class MatterCastingPlayer implements CastingPlayer {
3746
private int productId;
3847
private int vendorId;
3948
private long deviceType;
49+
protected long _cppCastingPlayer;
4050

4151
public MatterCastingPlayer(
4252
boolean connected,
@@ -137,4 +147,43 @@ public boolean equals(Object o) {
137147
MatterCastingPlayer that = (MatterCastingPlayer) o;
138148
return Objects.equals(this.deviceId, that.deviceId);
139149
}
150+
151+
/**
152+
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
153+
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
154+
* disk, this will execute the user directed commissioning process.
155+
*
156+
* @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window
157+
* open, if commissioning is required. Needs to be >= MIN_CONNECTION_TIMEOUT_SEC.
158+
* @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint
159+
* that the client wants to interact with after commissioning. If this value is passed in, the
160+
* VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired
161+
* Endpoint is not found in the on device CastingStore.
162+
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
163+
* The CompletableFuture will be completed with a Void value if the
164+
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
165+
* completed with an Exception. The Exception will be of type
166+
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
167+
* CastingException will contain the error code and message from the CastingApp.
168+
*/
169+
@Override
170+
public native CompletableFuture<Void> VerifyOrEstablishConnection(
171+
long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter);
172+
173+
/**
174+
* Verifies that a connection exists with this CastingPlayer, or triggers a new session request.
175+
* If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on
176+
* disk, this will execute the user directed commissioning process.
177+
*
178+
* @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed.
179+
* The CompletableFuture will be completed with a Void value if the
180+
* VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be
181+
* completed with an Exception. The Exception will be of type
182+
* com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the
183+
* CastingException will contain the error code and message from the CastingApp.
184+
*/
185+
@Override
186+
public CompletableFuture<Void> VerifyOrEstablishConnection() {
187+
return VerifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null);
188+
}
140189
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.support;
18+
19+
/** A class to describe a Matter device type. */
20+
public class DeviceTypeStruct {
21+
public long deviceType;
22+
public int revision;
23+
24+
public DeviceTypeStruct(long deviceType, int revision) {
25+
this.deviceType = deviceType;
26+
this.revision = revision;
27+
}
28+
}

0 commit comments

Comments
 (0)