package hk.hku.cs.srli.supermonkey.service; import android.app.Service; import android.content.Intent; import android.os.AsyncTask; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; /** * An asynchronous socket client service. */ public class SocketService extends Service { private Handler handler = new Handler(); private final IBinder binder = new SocketBinder(); private SocketListener listener = new SocketListener() { // An empty listener used as a placeholder. @Override public void onConnected() {} @Override public void onIncomingData(String data) {} @Override public void onDisconnected() {} @Override public void onError(String message) {} }; private ClientThread client; @Override public void onDestroy() { if (client != null) client.stop(); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return binder; } public class SocketBinder extends Binder { public void setListener(SocketListener listener) { // Wrap callback with adapter. SocketService.this.listener = new ListenerAdapter(listener); } public void connect(String host, int port) { if (client != null) client.stop(); // Close existing connection. client = new ClientThread(host, port); new Thread(client).start(); } public void send(String data) { if (client != null) client.send(data); } public boolean isConnected() { return client != null && client.isConnected(); } public void close() { if (client != null) client.stop(); } } public interface SocketListener { public void onConnected(); public void onIncomingData(String data); public void onDisconnected(); public void onError(String message); } /** * Adapter for running listener callback on main thread. * Intended to be called by the socket client thread. */ private class ListenerAdapter implements SocketListener{ private SocketListener listener; public ListenerAdapter(SocketListener listener) { this.listener = listener; } public void onConnected() { handler.post(new Runnable() { @Override public void run() {listener.onConnected();} }); } public void onIncomingData(final String data) { handler.post(new Runnable() { @Override public void run() {listener.onIncomingData(data);} }); } public void onDisconnected() { handler.post(new Runnable() { @Override public void run() {listener.onDisconnected();} }); } public void onError(final String message) { handler.post(new Runnable() { @Override public void run() {listener.onError(message);} }); } } private class ClientThread implements Runnable { private Socket socket; private PrintWriter out; private BufferedReader in; private boolean running; private String host; private int port; public ClientThread(String host, int port) { this.host = host; this.port = port; this.socket = new Socket(); this.running = false; } @Override public void run() { running = true; Log.v("SocketService.ClientThread", "Start running"); try { InetAddress dstAddress = InetAddress.getByName(host); InetSocketAddress socketAddr = new InetSocketAddress(dstAddress, port); socket.connect(socketAddr); Log.v("SocketService.ClientThread", "Connected!"); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); listener.onConnected(); while (running) { String message = in.readLine(); if (message == null) break; // The socket is disconnected. listener.onIncomingData(message); } } catch (IOException e) { Log.d("SocketService.ClientThread.run1", e.getMessage()); // Report error only when the socket is connected. if (running) listener.onError(e.getMessage()); } finally { try { socket.close(); out.close(); in.close(); } catch (NullPointerException e) { } catch (IOException e) { Log.d("SocketService.ClientThread.run2", e.getMessage()); } running = false; listener.onDisconnected(); Log.v("SocketService.ClientThread", "Stopped."); } } public void send(String data) { if (out == null) return; // Send data in another thread because it might block. new AsyncTask<String, Void, Void>() { @Override protected Void doInBackground(String... params) { String data = params[0]; out.println(data); Log.v("SocketService.ClientThread", "Send data: " + data); return null; } }.execute(data); } public void stop() { if (!running) return; Log.v("SocketService.ClientThread", "Stopping..."); running = false; try { // Force close the socket from main thread to unblock the client thread. socket.close(); } catch (IOException e) { Log.d("SocketService.ClientThread.stop", e.getMessage()); } } public boolean isConnected() { return running && socket.isConnected(); } } }