/**
* Squidy Interaction Library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Squidy Interaction Library is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy Interaction Library. If not, see
* <http://www.gnu.org/licenses/>.
*
* 2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website
* <http://www.squidy-lib.de> for further information.
*/
package org.squidy.nodes;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.squidy.SquidyException;
import org.squidy.common.util.ReflectionUtil;
import org.squidy.manager.ProcessException;
import org.squidy.manager.controls.CheckBox;
import org.squidy.manager.controls.TextField;
import org.squidy.manager.data.DataConstant;
import org.squidy.manager.data.IData;
import org.squidy.manager.data.Processor;
import org.squidy.manager.data.Property;
import org.squidy.manager.data.impl.DataButton;
import org.squidy.manager.data.impl.DataPosition2D;
import org.squidy.manager.model.AbstractNode;
import org.squidy.manager.protocol.osc.OSCListener;
import org.squidy.manager.protocol.osc.OSCServer;
import com.apple.dnssd.DNSSD;
import com.apple.dnssd.DNSSDException;
import com.apple.dnssd.DNSSDRegistration;
import com.apple.dnssd.DNSSDService;
import com.apple.dnssd.RegisterListener;
import com.illposed.osc.Endian;
import com.illposed.osc.OSCMessage;
/**
* <code>Android</code>.
*
* <pre>
* Date: December 14, 2009
* Time: 1:34:12 PM
* </pre>
*
* @author Roman Rädle, <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>, University of Konstanz
*
* @version $Id: Android.java 772 2011-09-16 15:39:44Z raedle $
* @since 1.0.0
*/
@XmlType(name = "Android")
@Processor(
name = "Android",
types = { Processor.Type.INPUT, Processor.Type.OUTPUT },
icon = "/org/squidy/nodes/image/48x48/android.png",
description = "/org/squidy/nodes/html/Android.html",
tags = { "Motorola", "Droid", "Android", "Smartphone", "touch", "multi-touch", "multitouch" }
)
public class Android extends AbstractNode implements RegisterListener {
// Logger to log info, error, debug,... messages.
private static final Log LOG = LogFactory.getLog(Android.class);
// Data constants sent from Squidy reference implementation on the Android.
public static final DataConstant TOUCHES_BEGAN = DataConstant.get(String.class, "TOUCHES_BEGAN");
public static final DataConstant TOUCHES_MOVED = DataConstant.get(String.class, "TOUCHES_MOVED");
public static final DataConstant TOUCHES_ENDED = DataConstant.get(String.class, "TOUCHES_ENDED");
public static final DataConstant TOUCHES_CANCELLED = DataConstant.get(String.class, "TOUCHES_CANCELLED");
public static final DataConstant TAP_COUNT = DataConstant.get(Integer.class, "TAP_COUNT");
public static final String SERVICE_TYPE_UDP = "_squidy-client-app._udp";
// ################################################################################
// BEGIN OF ADJUSTABLES
// ################################################################################
@XmlAttribute(name = "service-name")
@Property(
name = "Service name",
description = "Service name under which an Android can connect to the current Android node."
)
@TextField
private String serviceName = "Squidy Android";
/**
* @return the serviceName
*/
public final String getServiceName() {
return serviceName;
}
/**
* @param serviceName
* the serviceName to set
*/
public final void setServiceName(String serviceName) {
this.serviceName = serviceName;
if (isProcessing()) {
stopDNSSDService();
startDNSSDService();
}
}
@XmlAttribute(name = "port")
@Property(name = "Port", group = "Connection Settings")
@TextField
private int port = 2020;
/**
* @return the port
*/
public final int getPort() {
return port;
}
/**
* @param port
* the port to set
*/
public final void setPort(int port) {
this.port = port;
if (isProcessing()) {
stopOSCServer();
startOSCServer();
}
}
@XmlAttribute(name = "should-release-buttons")
@Property(name = "Should release buttons", group = "Options", description = "Whether the Android node should release buttons or not.")
@CheckBox
private boolean releaseButtonOnSingleTouch = true;
/**
* @return the releaseButtonOnSingleTouch
*/
public final boolean isReleaseButtonOnSingleTouch() {
return releaseButtonOnSingleTouch;
}
/**
* @param releaseButtonOnSingleTouch
* the releaseButtonOnSingleTouch to set
*/
public final void setReleaseButtonOnSingleTouch(
boolean releaseButtonOnSingleTouch) {
this.releaseButtonOnSingleTouch = releaseButtonOnSingleTouch;
}
// ################################################################################
// END OF ADJUSTABLES
// ################################################################################
// ################################################################################
// BEGIN OF DNS SD REGISTER LISTENER
// ################################################################################
/*
* (non-Javadoc)
*
* @seecom.apple.dnssd.RegisterListener#serviceRegistered(com.apple.dnssd.
* DNSSDRegistration, int, java.lang.String, java.lang.String,
* java.lang.String)
*/
public void serviceRegistered(DNSSDRegistration dnssdRegistration,
int flags, String serviceName, String regType, String domain) {
if (LOG.isDebugEnabled()) {
LOG.debug("Registered dns sd service " + SERVICE_TYPE_UDP
+ " [flags=" + flags + ",serviceName=" + serviceName
+ ",regType=" + regType + ",domain=" + domain + "]");
}
this.dnssdService = dnssdRegistration;
}
/*
* (non-Javadoc)
*
* @see
* com.apple.dnssd.BaseListener#operationFailed(com.apple.dnssd.DNSSDService
* , int)
*/
public void operationFailed(DNSSDService dnssdService, int errorCode) {
publishFailure(new SquidyException("Registration of dns sd service "
+ SERVICE_TYPE_UDP + " failed [error code=" + errorCode + "]"));
this.dnssdService = dnssdService;
// Stopping dns sd service.
stopDNSSDService();
}
// ################################################################################
// END OF DNS SD REGISTER LISTENER
// ################################################################################
private DNSSDService dnssdService;
// The OSC server to receive Android data.
private OSCServer oscServer;
private ServerSocket tcpServer;
private Map<Socket, DataOutputStream> outputStreams = new ConcurrentHashMap<Socket, DataOutputStream>();
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStart()
*/
@Override
public void onStart() throws ProcessException {
// Registering dns sd service.
startDNSSDService();
startOSCServer();
}
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStop()
*/
@Override
public void onStop() throws ProcessException {
stopOSCServer();
// Stopping dns sd service.
stopDNSSDService();
}
/**
*
*/
private void startDNSSDService() {
try {
dnssdService = DNSSD.register(serviceName, SERVICE_TYPE_UDP, port,
this);
} catch (DNSSDException e) {
// TODO [RR]: Uncomment if Bonjour has to be installed.
// publishFailure(e);
if (LOG.isErrorEnabled()) {
LOG.error(e);
}
} catch (Error e) {
if (LOG.isWarnEnabled()) {
LOG.warn(e);
}
}
}
/**
*
*/
private void stopDNSSDService() {
if (dnssdService != null) {
dnssdService.stop();
dnssdService = null;
if (LOG.isDebugEnabled()) {
LOG.debug("Stopped dns sd service " + SERVICE_TYPE_UDP);
}
}
}
/**
*
*/
private void startOSCServer() {
oscServer = new OSCServer(port, Endian.LITTLE_ENDIAN);
oscServer.addOSCListener("/squidy/bridge/osc", new OSCListener() {
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.protocol.osc.OSCListener#handleMessages
* (com.illposed.osc.OSCMessage[])
*/
public void handleMessages(OSCMessage[] messages) {
List<IData> datas = new ArrayList<IData>();
for (OSCMessage message : messages) {
Object[] arguments = message.getArguments();
IData data = ReflectionUtil
.createInstance((String) arguments[0]);
Object[] rawData = new Object[arguments.length - 1];
System.arraycopy(arguments, 1, rawData, 0, rawData.length);
data.deserialize(rawData);
if (data instanceof DataPosition2D) {
long timestamp = System.currentTimeMillis();
data.setTimestamp(timestamp);
if (releaseButtonOnSingleTouch && data.hasAttribute(TAP_COUNT)) {
if (messages.length == 1) {
int tapCount = (Integer) data.getAttribute(TAP_COUNT);
for (int i = 0; i < tapCount; i++) {
if (data.hasAttribute(TOUCHES_ENDED)) {
datas.add(new DataButton(Android.class,
DataButton.BUTTON_1, true));
datas.add(new DataButton(Android.class,
DataButton.BUTTON_1, false));
}
}
}
}
}
datas.add(data);
}
// Publish received data.
publish(datas);
}
});
oscServer.startListening();
}
/**
*
*/
private void stopOSCServer() {
if (oscServer != null) {
oscServer.stopListening();
oscServer.close();
oscServer = null;
}
}
}