diff --git a/examples/tv-casting-app/android/App/.idea/gradle.xml b/examples/tv-casting-app/android/App/.idea/gradle.xml
index 526b4c25c6813e..a2d7c21338e98a 100644
--- a/examples/tv-casting-app/android/App/.idea/gradle.xml
+++ b/examples/tv-casting-app/android/App/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java
index 5ff612685c515b..bd082444aa0521 100644
--- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java
+++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CommissionerDiscoveryFragment.java
@@ -4,14 +4,16 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
+import android.util.Log;;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
-import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import com.chip.casting.DiscoveredNodeData;
import com.chip.casting.FailureCallback;
@@ -19,132 +21,177 @@
import com.chip.casting.SuccessCallback;
import com.chip.casting.TvCastingApp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
/** A {@link Fragment} to discover commissioners on the network */
public class CommissionerDiscoveryFragment extends Fragment {
- private static final String TAG = CommissionerDiscoveryFragment.class.getSimpleName();
- private static final long DISCOVERY_DURATION_SECS = 10;
- private final TvCastingApp tvCastingApp;
-
- public CommissionerDiscoveryFragment(TvCastingApp tvCastingApp) {
- this.tvCastingApp = tvCastingApp;
- }
-
- /**
- * Use this factory method to create a new instance of this fragment using the provided
- * parameters.
- *
- * @return A new instance of fragment CommissionerDiscoveryFragment.
- */
- public static CommissionerDiscoveryFragment newInstance(TvCastingApp tvCastingApp) {
- return new CommissionerDiscoveryFragment(tvCastingApp);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_commissioner_discovery, container, false);
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- Button manualCommissioningButton = getView().findViewById(R.id.manualCommissioningButton);
- Callback callback = (Callback) this.getActivity();
- View.OnClickListener manualCommissioningButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- callback.handleCommissioningButtonClicked(null);
- }
- };
- manualCommissioningButton.setOnClickListener(manualCommissioningButtonOnClickListener);
-
- Context context = this.getContext();
- SuccessCallback successCallback =
- new SuccessCallback() {
- @Override
- public void handle(DiscoveredNodeData discoveredNodeData) {
- Log.d(TAG, "Discovered a Video Player Commissioner: " + discoveredNodeData);
- String buttonText = getCommissionerButtonText(discoveredNodeData);
-
- if (!buttonText.isEmpty()) {
- Button commissionerButton = new Button(context);
- commissionerButton.setText(buttonText);
- CommissionerDiscoveryFragment.Callback callback =
- (CommissionerDiscoveryFragment.Callback) getActivity();
- commissionerButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(
- TAG,
- "CommissionerResolveListener.onServiceResolved.OnClickListener.onClick called for "
- + discoveredNodeData);
- callback.handleCommissioningButtonClicked(discoveredNodeData);
- }
- });
- new Handler(Looper.getMainLooper())
- .post(
- () ->
- ((LinearLayout) getActivity().findViewById(R.id.castingCommissioners))
- .addView(commissionerButton));
+ private static final String TAG = CommissionerDiscoveryFragment.class.getSimpleName();
+ private static final long DISCOVERY_POLL_INTERVAL_MS = 15000;
+ private static final List commissionerVideoPlayerList = new ArrayList<>();
+ private FailureCallback failureCallback;
+ private SuccessCallback successCallback;
+ private ScheduledFuture poller;
+ private final TvCastingApp tvCastingApp;
+ private ScheduledExecutorService executor;
+
+ public CommissionerDiscoveryFragment(TvCastingApp tvCastingApp) {
+ this.tvCastingApp = tvCastingApp;
+ this.executor = Executors.newSingleThreadScheduledExecutor();
+ }
+
+ /**
+ * Use this factory method to create a new instance of this fragment using the provided
+ * parameters.
+ *
+ * @return A new instance of fragment CommissionerDiscoveryFragment.
+ */
+ public static CommissionerDiscoveryFragment newInstance(TvCastingApp tvCastingApp) {
+ return new CommissionerDiscoveryFragment(tvCastingApp);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_commissioner_discovery, container, false);
}
- }
- };
-
- FailureCallback failureCallback =
- new FailureCallback() {
- @Override
- public void handle(MatterError matterError) {
- Log.e(TAG, "Error occurred during video player commissioner discovery: " + matterError);
- }
- };
-
- Button discoverButton = getView().findViewById(R.id.discoverButton);
- discoverButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "Discovering on button click");
- tvCastingApp.discoverVideoPlayerCommissioners(
- DISCOVERY_DURATION_SECS, successCallback, failureCallback);
- }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ Button manualCommissioningButton = getView().findViewById(R.id.manualCommissioningButton);
+ // In the ideal case we wouldn't rely on the host activity to maintain this object since
+ // the lifecycle of the context isn't tied to this callback. Since this is an example app
+ // this should work fine.
+ Callback callback = (Callback) this.getActivity();
+ View.OnClickListener manualCommissioningButtonOnClickListener =
+ v -> callback.handleCommissioningButtonClicked(null);
+ manualCommissioningButton.setOnClickListener(manualCommissioningButtonOnClickListener);
+ ArrayAdapter arrayAdapter = new VideoPlayerCommissionerAdapter(getActivity(), commissionerVideoPlayerList);
+ final ListView list = getActivity().findViewById(R.id.commissionerList);
+ list.setAdapter(arrayAdapter);
+ list.setOnItemClickListener((parent, view1, position, id) -> {
+ DiscoveredNodeData discoveredNodeData = (DiscoveredNodeData)parent.getItemAtPosition(position);
+ Log.d(
+ TAG,
+ "OnItemClickListener.onClick called for "
+ + discoveredNodeData);
+ Callback callback1 =
+ (Callback) getActivity();
+ callback1.handleCommissioningButtonClicked(discoveredNodeData);
});
- Log.d(TAG, "Auto discovering");
- tvCastingApp.discoverVideoPlayerCommissioners(
- DISCOVERY_DURATION_SECS, successCallback, failureCallback);
- }
-
- @VisibleForTesting
- public String getCommissionerButtonText(DiscoveredNodeData commissioner) {
- String main = commissioner.getDeviceName() != null ? commissioner.getDeviceName() : "";
- String aux =
- "" + (commissioner.getProductId() > 0 ? "Product ID: " + commissioner.getProductId() : "");
- aux +=
- commissioner.getDeviceType() > 0
+ this.successCallback =
+ new SuccessCallback() {
+ @Override
+ public void handle(DiscoveredNodeData discoveredNodeData) {
+ Log.d(TAG, "Discovered a Video Player Commissioner: " + discoveredNodeData);
+ new Handler(Looper.getMainLooper())
+ .post(() -> {
+ if (commissionerVideoPlayerList.contains(discoveredNodeData)) {
+ Log.d(TAG, "Replacing existing entry in players list");
+ arrayAdapter.remove(discoveredNodeData);
+ }
+ arrayAdapter.add(discoveredNodeData);
+ });
+ }
+ };
+
+ this.failureCallback =
+ new FailureCallback() {
+ @Override
+ public void handle(MatterError matterError) {
+ Log.e(TAG, "Error occurred during video player commissioner discovery: " + matterError);
+ if (MatterError.DISCOVERY_SERVICE_LOST == matterError) {
+ Log.d(TAG, "Attempting to restart service");
+ tvCastingApp.discoverVideoPlayerCommissioners(successCallback, this);
+ }
+ }
+ };
+
+ Button discoverButton = getView().findViewById(R.id.discoverButton);
+ discoverButton.setOnClickListener(
+ v -> {
+ Log.d(TAG, "Discovering on button click");
+ tvCastingApp.discoverVideoPlayerCommissioners(successCallback, failureCallback);
+ });
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ Log.d(TAG, "Auto discovering");
+
+ poller = executor
+ .scheduleAtFixedRate(
+ () -> {
+ tvCastingApp.discoverVideoPlayerCommissioners(successCallback, failureCallback);
+ },
+ 0,
+ DISCOVERY_POLL_INTERVAL_MS,
+ TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ tvCastingApp.stopVideoPlayerDiscovery();
+ poller.cancel(true);
+ }
+
+ /** Interface for notifying the host. */
+ public interface Callback {
+ /** Notifies listener of Commissioning Button click. */
+ void handleCommissioningButtonClicked(DiscoveredNodeData selectedCommissioner);
+ }
+}
+
+class VideoPlayerCommissionerAdapter extends ArrayAdapter {
+ private final List playerList;
+ private LayoutInflater inflater;
+
+ public VideoPlayerCommissionerAdapter(Context applicationContext, List playerList) {
+ super(applicationContext, 0, playerList);
+ this.playerList = playerList;
+ inflater = (LayoutInflater.from(applicationContext));
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ view = inflater.inflate(R.layout.commissionable_player_list_item, null);
+ String buttonText = getCommissionerButtonText(playerList.get(i));
+ TextView playerDescription = view.findViewById(R.id.commissionable_player_description);
+ playerDescription.setText(buttonText);
+ return view;
+ }
+
+ private String getCommissionerButtonText(DiscoveredNodeData commissioner) {
+ String main = commissioner.getDeviceName() != null ? commissioner.getDeviceName() : "";
+ String aux =
+ "" + (commissioner.getProductId() > 0 ? "Product ID: " + commissioner.getProductId() : "");
+ aux +=
+ commissioner.getDeviceType() > 0
? (aux.isEmpty() ? "" : " ") + "Device Type: " + commissioner.getDeviceType()
: "";
- aux +=
- commissioner.getVendorId() > 0
+ aux +=
+ commissioner.getVendorId() > 0
? (aux.isEmpty() ? "" : " from ") + "Vendor ID: " + commissioner.getVendorId()
: "";
- aux = aux.isEmpty() ? aux : "\n[" + aux + "]";
-
- String preCommissioned = commissioner.isPreCommissioned() ? " (Pre-commissioned)" : "";
- return main + aux + preCommissioned;
- }
+ aux = aux.isEmpty() ? aux : "\n[" + aux + "]";
- /** Interface for notifying the host. */
- public interface Callback {
- /** Notifies listener of Commissioning Button click. */
- void handleCommissioningButtonClicked(DiscoveredNodeData selectedCommissioner);
- }
+ String preCommissioned = commissioner.isPreCommissioned() ? " (Pre-commissioned)" : "";
+ return main + aux + preCommissioned;
+ }
}
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java
index e3bc55388658ed..2a86e0743b1b88 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java
@@ -18,11 +18,13 @@
package com.chip.casting;
import android.net.nsd.NsdServiceInfo;
+
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
public class DiscoveredNodeData {
private static final int MAX_IP_ADDRESSES = 5;
@@ -185,4 +187,21 @@ public String toString() {
+ ipAddresses
+ '}';
}
+
+ // autogenerated
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(vendorId, productId, commissioningMode, deviceType);
+ result = 31 * result + Arrays.hashCode(rotatingId);
+ return result;
+ }
+
+ // autogenerated
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DiscoveredNodeData that = (DiscoveredNodeData) o;
+ return vendorId == that.vendorId && productId == that.productId && commissioningMode == that.commissioningMode && deviceType == that.deviceType && Objects.equals(hostName, that.hostName);
+ }
}
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/MatterError.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/MatterError.java
index aaaf95911f18da..ba40ec57831918 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/MatterError.java
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/MatterError.java
@@ -23,6 +23,9 @@ public class MatterError {
private int errorCode;
private String errorMessage;
+ public static final MatterError DISCOVERY_SERVICE_LOST =
+ new MatterError(4, "Discovery service was lost.");
+
public static final MatterError NO_ERROR = new MatterError(0, null);
public MatterError(int errorCode, String errorMessage) {
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/NsdDiscoveryListener.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/NsdDiscoveryListener.java
index ef5d66a6b5b566..18e0c63986aecd 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/NsdDiscoveryListener.java
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/NsdDiscoveryListener.java
@@ -38,13 +38,13 @@ public class NsdDiscoveryListener implements NsdManager.DiscoveryListener {
private final ExecutorService resolutionExecutor;
public NsdDiscoveryListener(
- NsdManager nsdManager,
- String targetServiceType,
- List deviceTypeFilter,
- List preCommissionedVideoPlayers,
- SuccessCallback successCallback,
- FailureCallback failureCallback,
- NsdManagerServiceResolver.NsdManagerResolverAvailState nsdManagerResolverAvailState) {
+ NsdManager nsdManager,
+ String targetServiceType,
+ List deviceTypeFilter,
+ List preCommissionedVideoPlayers,
+ SuccessCallback successCallback,
+ FailureCallback failureCallback,
+ NsdManagerServiceResolver.NsdManagerResolverAvailState nsdManagerResolverAvailState) {
this.nsdManager = nsdManager;
this.targetServiceType = targetServiceType;
this.deviceTypeFilter = deviceTypeFilter;
@@ -95,6 +95,7 @@ public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
Log.e(TAG, "Service lost: " + service);
+ failureCallback.handle(MatterError.DISCOVERY_SERVICE_LOST);
}
@Override
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
index 706673bdca1343..370c10f60d8eb9 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
@@ -33,6 +33,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TvCastingApp {
@@ -44,6 +45,11 @@ public class TvCastingApp {
private Context applicationContext;
private ChipAppServer chipAppServer;
private NsdManagerServiceResolver.NsdManagerResolverAvailState nsdManagerResolverAvailState;
+ private boolean discoveryStarted = false;
+
+ private WifiManager.MulticastLock multicastLock;
+ private NsdManager nsdManager;
+ private NsdDiscoveryListener nsdDiscoveryListener;
public boolean initApp(Context applicationContext, AppParameters appParameters) {
if (applicationContext == null || appParameters == null) {
@@ -103,21 +109,26 @@ public boolean initApp(Context applicationContext, AppParameters appParameters)
private native boolean initJni(AppParameters appParameters);
public void discoverVideoPlayerCommissioners(
- long discoveryDurationSeconds,
SuccessCallback discoverySuccessCallback,
FailureCallback discoveryFailureCallback) {
Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners called");
+ if (this.discoveryStarted)
+ {
+ Log.d(TAG, "Discovery already started, stopping before starting again");
+ stopVideoPlayerDiscovery();
+ }
+
List preCommissionedVideoPlayers = readCachedVideoPlayers();
WifiManager wifiManager =
(WifiManager) applicationContext.getSystemService(Context.WIFI_SERVICE);
- WifiManager.MulticastLock multicastLock = wifiManager.createMulticastLock("multicastLock");
+ multicastLock = wifiManager.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
- NsdManager nsdManager = (NsdManager) applicationContext.getSystemService(Context.NSD_SERVICE);
- NsdDiscoveryListener nsdDiscoveryListener =
+ nsdManager = (NsdManager) applicationContext.getSystemService(Context.NSD_SERVICE);
+ nsdDiscoveryListener =
new NsdDiscoveryListener(
nsdManager,
DISCOVERY_TARGET_SERVICE_TYPE,
@@ -128,24 +139,22 @@ public void discoverVideoPlayerCommissioners(
nsdManagerResolverAvailState);
nsdManager.discoverServices(
- DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener);
-
- Executors.newSingleThreadScheduledExecutor()
- .schedule(
- new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "TvCastingApp stopping Video Player commissioner discovery");
- nsdManager.stopServiceDiscovery(nsdDiscoveryListener);
- if(multicastLock.isHeld())
- {
- multicastLock.release();
- }
- }
- },
- discoveryDurationSeconds,
- TimeUnit.SECONDS);
- Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners ended");
+ DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener);
+ Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners started");
+ this.discoveryStarted = true;
+ }
+
+ public void stopVideoPlayerDiscovery()
+ {
+ Log.d(TAG, "TvCastingApp trying to stop video player discovery");
+ if (this.discoveryStarted && nsdManager != null && multicastLock != null && nsdDiscoveryListener != null) {
+ Log.d(TAG, "TvCastingApp stopping Video Player commissioner discovery");
+ nsdManager.stopServiceDiscovery(nsdDiscoveryListener);
+ if (multicastLock.isHeld()) {
+ multicastLock.release();
+ }
+ this.discoveryStarted = false;
+ }
}
public native boolean openBasicCommissioningWindow(
diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/commissionable_player_list_item.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/commissionable_player_list_item.xml
new file mode 100644
index 00000000000000..979093e5efb8d8
--- /dev/null
+++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/commissionable_player_list_item.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml
index c65a647957b28e..b9645a288d6449 100644
--- a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml
+++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_commissioner_discovery.xml
@@ -31,6 +31,11 @@
android:layout_height="wrap_content"
android:text="@string/discovery_message_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
+
+
diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/CommissionerDiscoveryViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/CommissionerDiscoveryViewModel.swift
index 1a3f48564d5d0c..9f825b7e897c93 100644
--- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/CommissionerDiscoveryViewModel.swift
+++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/CommissionerDiscoveryViewModel.swift
@@ -38,7 +38,9 @@ class CommissionerDiscoveryViewModel: ObservableObject {
self.Log.info("discoveredCommissionerHandler called with \(commissioner)")
if(self.commissioners.contains(commissioner))
{
- self.Log.info("Skipping previously discovered commissioner \(commissioner.description)")
+ var index = self.commissioners.firstIndex(of: commissioner)
+ self.commissioners[index!] = commissioner
+ self.Log.info("Updating previously discovered commissioner \(commissioner.description)")
}
else if(commissioner.numIPs == 0)
{
@@ -70,7 +72,9 @@ class CommissionerDiscoveryViewModel: ObservableObject {
if(commissioner != nil){
if(self.commissioners.contains(commissioner!))
{
- self.Log.info("Skipping previously discovered commissioner \(commissioner!.description)")
+ var index = self.commissioners.firstIndex(of: commissioner!)
+ self.commissioners[index!] = commissioner!
+ self.Log.info("Updating previously discovered commissioner \(commissioner!.description)")
}
else
{