/*
* Copyright 2014 Sony Corporation
*/
package com.almalence.sony.cameraremote;
import android.util.Log;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
/**
* A SSDP client class for this sample application. This implementation keeps
* simple so that many developers understand quickly.
*/
public class SimpleSsdpClient {
private static final String TAG = SimpleSsdpClient.class.getSimpleName();
private static final int SSDP_RECEIVE_TIMEOUT = 10000; // msec
private static final int PACKET_BUFFER_SIZE = 1024;
private static final int SSDP_PORT = 1900;
private static final int SSDP_MX = 1;
private static final String SSDP_ADDR = "239.255.255.250";
private static final String SSDP_ST = "urn:schemas-sony-com:service:ScalarWebAPI:1";
/** Handler interface for SSDP search result. */
public interface SearchResultHandler {
/**
* Called when API server device is found. Note that it's performed by
* non-UI thread.
*
* @param device API server device that is found by searching
*/
void onDeviceFound(ServerDevice device);
/**
* Called when searching completes successfully. Note that it's
* performed by non-UI thread.
*/
void onFinished();
/**
* Called when searching completes with some errors. Note that it's
* performed by non-UI thread.
*/
void onErrorFinished();
}
private boolean mSearching = false;
/**
* Search API server device.
*
* @param handler result handler
* @return true: start successfully, false: already searching now
*/
public synchronized boolean search(final SearchResultHandler handler) {
if (mSearching) {
Log.w(TAG, "search() already searching.");
return false;
}
if (handler == null) {
throw new NullPointerException("handler is null.");
}
Log.i(TAG, "search() Start.");
final String ssdpRequest =
"M-SEARCH * HTTP/1.1\r\n" + String.format("HOST: %s:%d\r\n", SSDP_ADDR, SSDP_PORT)
+ String.format("MAN: \"ssdp:discover\"\r\n")
+ String.format("MX: %d\r\n", SSDP_MX)
+ String.format("ST: %s\r\n", SSDP_ST) + "\r\n";
final byte[] sendData = ssdpRequest.getBytes();
new Thread() {
@Override
public void run() {
// Send Datagram packets
DatagramSocket socket = null;
DatagramPacket receivePacket = null;
DatagramPacket packet = null;
try {
socket = new DatagramSocket();
InetSocketAddress iAddress = new InetSocketAddress(SSDP_ADDR, SSDP_PORT);
packet = new DatagramPacket(sendData, sendData.length, iAddress);
// send 3 times
Log.i(TAG, "search() Send Datagram packet 3 times.");
socket.send(packet);
Thread.sleep(100);
socket.send(packet);
Thread.sleep(100);
socket.send(packet);
} catch (SocketException e) {
Log.e(TAG, "search() DatagramSocket error:", e);
if (socket != null && !socket.isClosed()) {
socket.close();
}
handler.onErrorFinished();
return;
} catch (IOException e) {
Log.e(TAG, "search() IOException :", e);
if (socket != null && !socket.isClosed()) {
socket.close();
}
handler.onErrorFinished();
return;
} catch (InterruptedException e) {
// do nothing.
Log.d(TAG, "search() InterruptedException :", e);
}
// Receive reply packets
mSearching = true;
long startTime = System.currentTimeMillis();
List<String> foundDevices = new ArrayList<String>();
byte[] array = new byte[PACKET_BUFFER_SIZE];
ServerDevice device = null;
try {
while (mSearching) {
receivePacket = new DatagramPacket(array, array.length);
socket.setSoTimeout(SSDP_RECEIVE_TIMEOUT);
socket.receive(receivePacket);
String ssdpReplyMessage = new String(receivePacket.getData(), 0, //
receivePacket.getLength(), "UTF-8");
String ddUsn = findParameterValue(ssdpReplyMessage, "USN");
/*
* There is possibility to receive multiple packets from
* a individual server.
*/
if (!foundDevices.contains(ddUsn)) {
String ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION");
foundDevices.add(ddUsn);
// Fetch Device Description XML and parse it.
device = ServerDevice.fetch(ddLocation);
// Note that it's a irresponsible rule
// for the sample application.
if (device != null && device.hasApiService("camera")) {
handler.onDeviceFound(device);
}
}
if (SSDP_RECEIVE_TIMEOUT < System.currentTimeMillis() - startTime) {
break;
}
}
} catch (InterruptedIOException e) {
Log.d(TAG, "search() Timeout.");
if (device == null) {
mSearching = false;
handler.onErrorFinished();
return;
}
} catch (IOException e) {
Log.d(TAG, "search() IOException2. : " + e);
mSearching = false;
handler.onErrorFinished();
return;
} finally {
Log.d(TAG, "search() Finish ");
mSearching = false;
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
handler.onFinished();
};
}.start();
return true;
}
/**
* Checks whether searching is in progress or not.
*
* @return true: now searching, false: otherwise
*/
public boolean isSearching() {
return mSearching;
}
/**
* Cancels searching. Note that it cannot stop the operation immediately.
*/
public void cancelSearching() {
mSearching = false;
}
/**
* Find a value string from message line as below. (ex.)
* "ST: XXXXX-YYYYY-ZZZZZ" -> "XXXXX-YYYYY-ZZZZZ"
*/
private static String findParameterValue(String ssdpMessage, String paramName) {
String name = paramName;
if (!name.endsWith(":")) {
name = name + ":";
}
int start = ssdpMessage.indexOf(name);
int end = ssdpMessage.indexOf("\r\n", start);
if (start != -1 && end != -1) {
start += name.length();
String val = ssdpMessage.substring(start, end);
if (val != null) {
return val.trim();
}
}
return null;
}
}