package com.cellbots.logger;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.LinkedList;
public class RemoteControl {
private static final String TAG = "RemoteControl";
private final Context mContext;
private RemoteThread mThread = null;
public interface CommandListener {
boolean onCommandReceived(Command c) throws Exception;
}
@SuppressWarnings("serial")
private static class ListenerList extends LinkedList<CommandListener> {
}
@SuppressWarnings("serial")
private static class CommandMap extends HashMap<String, ListenerList> {
public void addCommand(String command, CommandListener listener) {
if (!containsKey(command)) {
this.put(command, new ListenerList());
}
this.get(command).add(listener);
}
public void removeCommand(String command, CommandListener listener) {
if (!containsKey(command))
return;
this.get(command).remove(listener);
}
public ListenerList getListeners(String command) {
if (!containsKey(command))
return null;
return this.get(command);
}
}
public static final class Command {
public String command;
private final SocketChannel client;
public Command(String command, SocketChannel client) {
this.command = command;
this.client = client;
}
public void sendResponse(String response) {
try {
ByteBuffer tmp = ByteBuffer.wrap(response.getBytes());
tmp.position(0);
tmp.limit(tmp.capacity());
client.write(tmp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static final int MESSAGE_COMMAND = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message m) {
if (m.what != MESSAGE_COMMAND)
return;
Command c = (Command) m.obj;
m.obj = null;
if (!mCommandMap.containsKey(c.command)) {
StringBuilder b = new StringBuilder(64);
b.append("Unknown Command: ").append(c.command).append('\n');
c.sendResponse(b.toString());
}
runListeners(c);
}
};
private CommandMap mCommandMap = new CommandMap();
public void registerCommandListener(String command, CommandListener listener) {
mCommandMap.addCommand(command, listener);
}
public void unregisterCommandListener(String command, CommandListener listener) {
mCommandMap.removeCommand(command, listener);
}
public void runListeners(Command c) {
ListenerList listeners = mCommandMap.getListeners(c.command);
if (listeners == null)
return;
for (CommandListener l : listeners) {
try {
if (l.onCommandReceived(c))
break;
} catch (Exception e) {
e.printStackTrace();
}
}
return;
}
public void broadcastMessage(String message) {
if (mThread == null)
return;
mThread.broadcastMessage(message);
}
public RemoteControl(Context context) {
mContext = context;
}
public void start() {
if (mThread != null)
return;
int port = mContext.getResources().getInteger(R.integer.network_remote_port);
mThread = new RemoteThread(port, mHandler);
mThread.start();
}
public void shutdown() {
if (mThread != null) {
mThread.shutdown();
mThread = null;
}
}
private static final class RemoteThread extends Thread {
private Selector mSelector = null;
private final Handler mHandler;
private final int mPort;
private boolean mShutdown = false;
private static final class Tag {
public boolean client = false;
public ByteBuffer buf = null;
}
public RemoteThread(int port, Handler handler) {
super("RemoteThread");
mPort = port;
mHandler = handler;
}
private ByteBuffer mOutBuf = ByteBuffer.allocateDirect(1024);
public void broadcastMessage(String msg) {
if (mSelector == null)
return;
synchronized (mOutBuf) {
mOutBuf.clear();
mOutBuf.put(msg.getBytes());
}
mSelector.wakeup();
}
public void shutdown() {
mShutdown = true;
mSelector.wakeup();
}
@Override
public void run() {
try {
mSelector = createSelector();
} catch (IOException e) {
e.printStackTrace();
return;
}
int tries = 0;
Log.i(TAG, "RemoteControl started and ready on port " + mPort);
Tag t;
while (!mShutdown) {
if (tries > 3)
return;
try {
Log.i(TAG, "Selecting...");
mSelector.select(2500);
} catch (ClosedSelectorException e) {
Log.e(TAG, "Somehow the selector got closed.");
return;
} catch (IOException e) {
Log.e(TAG, "Unable to select");
e.printStackTrace();
tries++;
continue;
}
if (mShutdown)
break;
for (SelectionKey k : mSelector.selectedKeys()) {
try {
if (k.isAcceptable()) {
acceptConnection(k);
}
if (k.isReadable()) {
if (!readClient(k)) {
k.cancel();
}
}
} catch (IOException e) {
e.printStackTrace();
k.cancel();
} finally {
mSelector.selectedKeys().remove(k);
}
if (mShutdown)
break;
}
if (mShutdown)
break;
synchronized (mOutBuf) {
if (mOutBuf.position() == 0)
continue;
for (SelectionKey k : mSelector.keys()) {
t = (Tag) k.attachment();
if (t == null)
continue;
if (t.client == false)
continue;
try {
SocketChannel chan = (SocketChannel) k.channel();
mOutBuf.flip();
chan.write(mOutBuf);
} catch (IOException e) {
Log.e(TAG, "Error broadcasting message");
e.printStackTrace();
continue;
} finally {
mOutBuf.rewind();
}
if (mShutdown)
break;
}
}
}
try {
for (SelectionKey k : mSelector.keys()) {
k.channel().close();
k.attach(null);
k.cancel();
}
mSelector.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
}
private boolean readClient(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
Tag tag = (Tag) key.attachment();
ByteBuffer buf = tag.buf;
int bytesRead = channel.read(buf);
if (bytesRead == -1) {
Log.i(TAG, "Client Disconnected: "
+ channel.socket().getRemoteSocketAddress().toString());
// Client disconnected
tag = null;
key.attach(null);
channel.close();
return false;
}
Log.d(TAG, "Received: " + bytesRead + " (" + buf.position() + " )");
if (buf.position() > 0 &&
(buf.get(buf.position() - 1) != '\n') && (buf.get(buf.position() - 1) != '\r'))
{
if (buf.position() > 0)
Log.d(TAG, "Last char: " + buf.get(buf.position() - 1));
return true;
}
StringBuilder b = new StringBuilder(buf.position());
for (int i = 0; i < buf.position() - 1; i++)
b.append((char) buf.get(i));
buf.put(buf.position() - 1, (byte) 0);
int pos = b.indexOf("\r");
while (pos != -1) {
b.deleteCharAt(pos);
pos = b.indexOf("\r");
}
Log.d(TAG, "Received: " + b.toString());
Message m = mHandler.obtainMessage();
m.what = RemoteControl.MESSAGE_COMMAND;
m.obj = new RemoteControl.Command(b.toString(), channel);
m.sendToTarget();
buf.clear();
return true;
}
private void acceptConnection(SelectionKey key) throws IOException {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
client.configureBlocking(false);
Tag t = new Tag();
t.buf = ByteBuffer.allocateDirect(512);
t.client = true;
SelectionKey clientKey = client.register(mSelector, SelectionKey.OP_READ);
clientKey.attach(t);
Log.i(TAG, "Client connected: " + client.socket().getRemoteSocketAddress());
}
private Selector createSelector() throws IOException {
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().setReuseAddress(true);
serverSocket.socket().bind(new InetSocketAddress(mPort));
serverSocket.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey k = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
Tag t = new Tag();
t.client = false;
k.attach(t);
return selector;
}
}
}