/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.example.google.tv.anymotelibrary.connection;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.SocketException;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
/**
* An implementation of a trivial broadcast discovery protocol.
* <p>
* This client sends L3 broadcasts to probe for particular services on the
* network.
*/
public class BroadcastDiscoveryClient implements Runnable {
private static final String LOG_TAG = "BroadcastDiscoveryClient";
/**
* UDP port to send probe messages to.
*/
private static final int BROADCAST_SERVER_PORT = 9101;
/**
* Frequency of probe messages.
*/
private static final int PROBE_INTERVAL_MS = 2000;
/**
* Command name for a discovery request.
*/
private static final String COMMAND_DISCOVER = "discover";
/**
* Broadcast address of the local device.
*/
private final Inet4Address mBroadcastAddress;
/**
* Timer to send probes.
*/
private final Timer mProbeTimer;
/**
* TimerTask to send probes.
*/
private final TimerTask mProbeTimerTask;
/**
* Send/receive socket.
*/
private final DatagramSocket mSocket;
private DeviceDiscoveredListener mListener;
private final String mServiceName;
/**
* Broadcast advertisement response to listener.
*/
public final class BroadcastAdvertisement {
/**
* Name of the service.
*/
private final String mAdvertisedName;
/**
* Address of the service.
*/
private final Inet4Address mAdvertisedAddress;
/**
* Port of the service.
*/
private final int mAdvertisedPort;
BroadcastAdvertisement(String name, Inet4Address addr, int port) {
mAdvertisedName = name;
mAdvertisedAddress = addr;
mAdvertisedPort = port;
}
/**
* Returns name of the advertised service.
*
* @return name
*/
public String getServiceName() {
return mAdvertisedName;
}
/**
* Returns address of the advertised service.
*
* @return address
*/
public Inet4Address getServiceAddress() {
return mAdvertisedAddress;
}
/**
* Returns port of the advertised service.
*
* @return port
*/
public int getServicePort() {
return mAdvertisedPort;
}
}
/**
* Device found listener interface.
*/
public interface DeviceDiscoveredListener {
/**
* Called when a Device is discovered on the network.
*
* @param advert The advertisement containing device information.
*/
void onDeviceDiscovered(BroadcastAdvertisement advert);
}
/**
* Constructor
*
* @param broadcastAddress destination address for probes.
* @param service The name of service we are looking for.
*/
public BroadcastDiscoveryClient(Inet4Address broadcastAddress, String service) {
mBroadcastAddress = broadcastAddress;
mServiceName = service;
try {
// binds to random port
mSocket = new DatagramSocket();
mSocket.setBroadcast(true);
} catch (SocketException e) {
Log.e(LOG_TAG, "Could not create broadcast client socket.", e);
throw new RuntimeException();
}
mProbeTimer = new Timer();
mProbeTimerTask = new TimerTask() {
@Override
public void run() {
BroadcastDiscoveryClient.this.sendProbe();
}
};
Log.i(LOG_TAG, "Starting client on address " + mBroadcastAddress);
}
public void run() {
Log.i(LOG_TAG, "Broadcast client thread starting.");
byte[] buffer = new byte[256];
mProbeTimer.schedule(mProbeTimerTask, 0, PROBE_INTERVAL_MS);
while (true) {
try {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
mSocket.receive(packet);
handleResponsePacket(packet);
} catch (InterruptedIOException e) {
// timeout
} catch (IOException e) {
// SocketException - stop() was called
mProbeTimer.cancel();
break;
}
}
Log.i(LOG_TAG, "Exiting client loop.");
mProbeTimer.cancel();
}
/**
* Sends a single broadcast discovery request.
*/
private void sendProbe() {
DatagramPacket packet = makeRequestPacket(mServiceName, mSocket.getLocalPort());
try {
mSocket.send(packet);
} catch (IOException e) {
Log.e(LOG_TAG, "Exception sending broadcast probe", e);
return;
}
}
/**
* Immediately stops the receiver thread, and cancels the probe timer.
*/
public void stop() {
if (mSocket != null) {
mSocket.close();
}
}
/**
* Constructs a new probe packet.
*
* @param serviceName the service name to discover
* @param responsePort the udp port number for replies
* @return a new DatagramPacket
*/
private DatagramPacket makeRequestPacket(String serviceName, int responsePort) {
String message = COMMAND_DISCOVER + " " + serviceName + " " + responsePort + "\n";
byte[] buf = message.getBytes();
DatagramPacket packet =
new DatagramPacket(buf, buf.length, mBroadcastAddress, BROADCAST_SERVER_PORT);
return packet;
}
/**
* Parse a received packet, and notify the main thread if valid.
*
* @param packet The locally-received DatagramPacket
*/
private void handleResponsePacket(DatagramPacket packet) {
String strPacket = new String(packet.getData(), 0, packet.getLength());
String tokens[] = strPacket.trim().split("\\s+");
if (tokens.length != 3) {
Log.w(LOG_TAG, "Malformed response: expected 3 tokens, got " + tokens.length);
return;
}
BroadcastAdvertisement advert;
try {
String serviceType = tokens[0];
if (!serviceType.equals(mServiceName)) {
return;
}
String serviceName = tokens[1];
int port = Integer.parseInt(tokens[2]);
Inet4Address addr = (Inet4Address) packet.getAddress();
Log.v(LOG_TAG, "Broadcast response: " + serviceName + ", " + addr + ", " + port);
advert = new BroadcastAdvertisement(serviceName, addr, port);
} catch (NumberFormatException e) {
return;
}
if (mListener != null) {
mListener.onDeviceDiscovered(advert);
}
}
/**
* Sets the device discovery listener for the client.
*
* @param listener device discovery listener.
*/
public void setDeviceDiscoveredListener(final DeviceDiscoveredListener listener) {
mListener = listener;
}
}