Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect updated DNS SD broadcast names in TV app #25355

Merged
merged 11 commits into from
Mar 8, 2023
1 change: 0 additions & 1 deletion examples/tv-casting-app/android/App/.idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,37 @@
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 androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import com.chip.casting.DiscoveredNodeData;
import com.chip.casting.FailureCallback;
import com.chip.casting.MatterError;
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 static final long DISCOVERY_POLL_INTERVAL_MS = 15000;
private static final List<DiscoveredNodeData> commissionerVideoPlayerList = new ArrayList<>();
private FailureCallback failureCallback;
private SuccessCallback<DiscoveredNodeData> successCallback;
private ScheduledFuture poller;
private final TvCastingApp tvCastingApp;
private ScheduledExecutorService executor;

public CommissionerDiscoveryFragment(TvCastingApp tvCastingApp) {
this.tvCastingApp = tvCastingApp;
this.executor = Executors.newSingleThreadScheduledExecutor();
}

/**
Expand Down Expand Up @@ -56,75 +68,117 @@ 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 =
new View.OnClickListener() {
@Override
public void onClick(View v) {
callback.handleCommissioningButtonClicked(null);
}
};
v -> callback.handleCommissioningButtonClicked(null);
manualCommissioningButton.setOnClickListener(manualCommissioningButtonOnClickListener);

Context context = this.getContext();
SuccessCallback<DiscoveredNodeData> successCallback =
ArrayAdapter<DiscoveredNodeData> arrayAdapter =
new VideoPlayerCommissionerAdapter(getActivity(), commissionerVideoPlayerList);
final ListView list = getActivity().findViewById(R.id.commissionerList);
list.setAdapter(arrayAdapter);

this.successCallback =
new SuccessCallback<DiscoveredNodeData>() {
@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));
}
new Handler(Looper.getMainLooper())
.post(
() -> {
if (commissionerVideoPlayerList.contains(discoveredNodeData)) {
Log.d(TAG, "Replacing existing entry in players list");
arrayAdapter.remove(discoveredNodeData);
}
arrayAdapter.add(discoveredNodeData);
});
}
};

FailureCallback failureCallback =
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(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Discovering on button click");
tvCastingApp.discoverVideoPlayerCommissioners(
DISCOVERY_DURATION_SECS, successCallback, failureCallback);
}
v -> {
Log.d(TAG, "Discovering on button click");
tvCastingApp.discoverVideoPlayerCommissioners(successCallback, failureCallback);
});
}

@Override
public void onResume() {
super.onResume();
Log.d(TAG, "Auto discovering");
tvCastingApp.discoverVideoPlayerCommissioners(
DISCOVERY_DURATION_SECS, successCallback, failureCallback);

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);
}

@VisibleForTesting
public String getCommissionerButtonText(DiscoveredNodeData commissioner) {
/** Interface for notifying the host. */
public interface Callback {
/** Notifies listener of Commissioning Button click. */
void handleCommissioningButtonClicked(DiscoveredNodeData selectedCommissioner);
}
}

class VideoPlayerCommissionerAdapter extends ArrayAdapter<DiscoveredNodeData> {
private final List<DiscoveredNodeData> playerList;
private final Context context;
private LayoutInflater inflater;
private static final String TAG = VideoPlayerCommissionerAdapter.class.getSimpleName();

public VideoPlayerCommissionerAdapter(Context context, List<DiscoveredNodeData> playerList) {
super(context, 0, playerList);
this.context = context;
this.playerList = playerList;
inflater = (LayoutInflater.from(context));
}

@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));
Button playerDescription = view.findViewById(R.id.commissionable_player_description);
playerDescription.setText(buttonText);
View.OnClickListener clickListener =
v -> {
DiscoveredNodeData discoveredNodeData = playerList.get(i);
Log.d(TAG, "OnItemClickListener.onClick called for " + discoveredNodeData);
CommissionerDiscoveryFragment.Callback callback1 =
(CommissionerDiscoveryFragment.Callback) context;
callback1.handleCommissioningButtonClicked(discoveredNodeData);
};
playerDescription.setOnClickListener(clickListener);
return view;
}

private String getCommissionerButtonText(DiscoveredNodeData commissioner) {
String main = commissioner.getDeviceName() != null ? commissioner.getDeviceName() : "";
String aux =
"" + (commissioner.getProductId() > 0 ? "Product ID: " + commissioner.getProductId() : "");
Expand All @@ -141,10 +195,4 @@ public String getCommissionerButtonText(DiscoveredNodeData commissioner) {
String preCommissioned = commissioner.isPreCommissioned() ? " (Pre-commissioned)" : "";
return main + aux + preCommissioned;
}

/** Interface for notifying the host. */
public interface Callback {
/** Notifies listener of Commissioning Button click. */
void handleCommissioningButtonClicked(DiscoveredNodeData selectedCommissioner);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public class ConnectionFragment extends Fragment {
private final TvCastingApp tvCastingApp;
private final DiscoveredNodeData selectedCommissioner;

private boolean verifyOrEstablishConnectionSuccess;
private boolean openCommissioningWindowSuccess;
private boolean sendUdcSuccess;

Expand Down Expand Up @@ -83,9 +82,8 @@ public void handle(ContentApp contentApp) {
if (selectedCommissioner != null && selectedCommissioner.isPreCommissioned()) {
VideoPlayer videoPlayer = selectedCommissioner.toConnectableVideoPlayer();
Log.d(TAG, "Calling verifyOrEstablishConnectionSuccess with VideoPlayer: " + videoPlayer);
this.verifyOrEstablishConnectionSuccess =
tvCastingApp.verifyOrEstablishConnection(
videoPlayer, onConnectionSuccess, onConnectionFailure, onNewOrUpdatedEndpoints);
tvCastingApp.verifyOrEstablishConnection(
videoPlayer, onConnectionSuccess, onConnectionFailure, onNewOrUpdatedEndpoints);
} else {
Log.d(TAG, "Running commissioning");
this.openCommissioningWindowSuccess =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class DiscoveredNodeData {
private static final String TAG = DiscoveredNodeData.class.getSimpleName();
Expand Down Expand Up @@ -216,4 +217,25 @@ 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
cliffamzn marked this conversation as resolved.
Show resolved Hide resolved
@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
import chip.platform.PreferencesKeyValueStoreManager;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TvCastingApp {
private static final String TAG = TvCastingApp.class.getSimpleName();
Expand All @@ -44,6 +42,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) {
Expand Down Expand Up @@ -103,21 +106,25 @@ public boolean initApp(Context applicationContext, AppParameters appParameters)
private native boolean initJni(AppParameters appParameters);

public void discoverVideoPlayerCommissioners(
long discoveryDurationSeconds,
SuccessCallback<DiscoveredNodeData> discoverySuccessCallback,
FailureCallback discoveryFailureCallback) {
Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners called");

if (this.discoveryStarted) {
Log.d(TAG, "Discovery already started, stopping before starting again");
stopVideoPlayerDiscovery();
}

List<VideoPlayer> 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,
Expand All @@ -129,22 +136,22 @@ public void discoverVideoPlayerCommissioners(

nsdManager.discoverServices(
DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener);
Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners started");
this.discoveryStarted = true;
}

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");
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;
cliffamzn marked this conversation as resolved.
Show resolved Hide resolved
}

public native boolean openBasicCommissioningWindow(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/commissionable_player_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp" />

</LinearLayout>
Loading