package com.o3dr.android.client.utils.connection; import android.os.Bundle; import android.os.Handler; import android.os.Process; import android.os.RemoteException; import android.util.Log; import com.o3dr.services.android.lib.model.ICommandListener; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * Base class for ip connection (tcp, udp). */ public abstract class AbstractIpConnection { private static final String TAG = AbstractIpConnection.class.getSimpleName(); public static final int CONNECTION_TIMEOUT = 15 * 1000; //5 seconds /* Connection state */ public static final int STATE_DISCONNECTED = 0; public static final int STATE_CONNECTING = 1; public static final int STATE_CONNECTED = 2; /** * Size of the buffer used to read messages from the connection. */ private static final int DEFAULT_READ_BUFFER_SIZE = 4096; private IpConnectionListener ipConnectionListener; /** * Queue the set of packets to send. * A thread will be blocking on it until there's element(s) available to send. */ private final LinkedBlockingQueue<PacketData> packetsToSend = new LinkedBlockingQueue<>(); private final AtomicInteger connectionStatus = new AtomicInteger(STATE_DISCONNECTED); private final AtomicReference<Bundle> extrasHolder = new AtomicReference<>(); private final boolean isSendingDisabled; private final boolean isReadingDisabled; private final ByteBuffer readBuffer; private final Runnable managerTask = new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); Thread sendingThread = null; try { try { open(extrasHolder.get()); connectionStatus.set(STATE_CONNECTED); if(ipConnectionListener != null) ipConnectionListener.onIpConnected(); } catch (IOException e) { Log.e(TAG, "Unable to open ip connection.", e); return; } if(!isSendingDisabled) { //Launch the packet dispatching thread sendingThread = new Thread(sendingTask, "IP Connection-Sending Thread"); sendingThread.start(); } if(!isReadingDisabled) { try { while (connectionStatus.get() == STATE_CONNECTED) { readBuffer.clear(); try { int packetSize = read(readBuffer); if (packetSize > 0) { readBuffer.limit(packetSize); if (ipConnectionListener != null) { readBuffer.rewind(); ipConnectionListener.onPacketReceived(readBuffer); } } }catch(InterruptedIOException e){ if(!isPolling) throw e; } } } catch (IOException e) { Log.e(TAG, "Error occurred while reading from the connection.", e); } } else if(sendingThread != null){ try { sendingThread.join(); } catch (InterruptedException e) { Log.e(TAG, "Error while waiting for sending thread to complete.", e); } } } finally{ if(sendingThread != null && sendingThread.isAlive()) sendingThread.interrupt(); disconnect(); Log.i(TAG, "Exiting connection manager thread."); } } }; /** * Blocks until there's packet(s) to send, then dispatch them. */ private final Runnable sendingTask = new Runnable() { @Override public void run() { try{ while(connectionStatus.get() == STATE_CONNECTED){ final PacketData packetData = packetsToSend.take(); final ICommandListener listener = packetData.listener; try { send(packetData); postSendSuccess(listener); } catch (IOException e) { Log.e(TAG, "Error occurred while sending packet.", e); postSendTimeout(listener); } } } catch (InterruptedException e) { Log.e(TAG, "Dispatching thread was interrupted.", e); } finally{ disconnect(); Log.i(TAG, "Exiting packet dispatcher thread."); } } private void postSendSuccess(final ICommandListener listener){ if(handler == null || listener == null) return; handler.post(new Runnable() { @Override public void run() { try { listener.onSuccess(); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } } }); } private void postSendTimeout(final ICommandListener listener){ if(handler == null || listener == null) return; handler.post(new Runnable() { @Override public void run() { try { listener.onTimeout(); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } } }); } }; private final boolean isPolling; private final Handler handler; private Thread managerThread; public AbstractIpConnection(Handler handler){ this(handler, false, false); } public AbstractIpConnection(Handler handler, int readBufferSize, boolean isPolling){ this(handler, readBufferSize, false, false, isPolling); } public AbstractIpConnection(Handler handler, boolean disableSending, boolean disableReading){ this(handler, DEFAULT_READ_BUFFER_SIZE, disableSending, disableReading, false); } public AbstractIpConnection(Handler handler, int readBufferSize, boolean disableSending, boolean disableReading, boolean isPolling){ this.handler = handler; this.readBuffer = ByteBuffer.allocate(readBufferSize); isReadingDisabled = disableReading; isSendingDisabled = disableSending; this.isPolling = isPolling; } protected abstract void open(Bundle extras) throws IOException; protected abstract int read(ByteBuffer buffer) throws IOException; protected abstract void send(PacketData data) throws IOException; protected abstract void close() throws IOException; /** * Establish an ip connection. If successful, ConnectionListener#onIpConnected() is called. * @param extras */ public void connect(Bundle extras){ if(connectionStatus.compareAndSet(STATE_DISCONNECTED, STATE_CONNECTING)){ Log.i(TAG, "Starting manager thread."); extrasHolder.set(extras); managerThread = new Thread(managerTask, "IP Connection-Manager Thread"); managerThread.setPriority(Thread.MAX_PRIORITY); managerThread.start(); } } public Bundle getConnectionExtras(){ return extrasHolder.get(); } /** * Disconnect an existing ip connection. If successful, ConnectionListener#onIpDisconnected() is called. */ public void disconnect(){ if(connectionStatus.get() == STATE_DISCONNECTED || managerThread == null) return; connectionStatus.set(STATE_DISCONNECTED); if(managerThread != null && managerThread.isAlive() && !managerThread.isInterrupted()){ managerThread.interrupt(); } try { close(); } catch (IOException e) { Log.e(TAG, "Error occurred while closing ip connection.", e); } if(ipConnectionListener != null) ipConnectionListener.onIpDisconnected(); } public void setIpConnectionListener(IpConnectionListener ipConnectionListener) { this.ipConnectionListener = ipConnectionListener; } public void sendPacket(byte[] packet, int packetSize, ICommandListener listener){ if(packet == null || packetSize <= 0) return; packetsToSend.offer(new PacketData(packetSize, packet, listener)); } public int getConnectionStatus(){ return connectionStatus.get(); } protected static final class PacketData { public final int dataLength; public final byte[] data; public final ICommandListener listener; public PacketData(int dataLength, byte[] data, ICommandListener listener) { this.dataLength = dataLength; this.data = data; this.listener = listener; } } }