package dk.silverbullet.telemed.device.continua.android;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import dk.silverbullet.telemed.device.continua.EndOfFileException;
import dk.silverbullet.telemed.device.continua.PacketCollector;
import dk.silverbullet.telemed.utils.Util;
/**
* Listens on the input stream from the connected Bluetooth device, and allows sending bytes to the device. Is
* completely agnostic as to what is sent back and forth.
*
* Received bytes are simply piped to the given {@see PacketCollector}.
*/
public class BluetoothStreamer extends Thread {
private static final String TAG = Util.getTag(BluetoothStreamer.class);
private final Object parcelFileDescriptorSemaphore = new Object();
private PacketCollector packetCollector;
private ParcelFileDescriptor parcelFileDescriptor;
private static final long RESET_TIME = 100;
private long resetTime;
private boolean resetCollector;
public void setPacketCollector(PacketCollector packetCollector) {
this.packetCollector = packetCollector;
}
public void startReading() {
Log.d(TAG, "startReading");
if (!isAlive()) {
start();
}
}
public void stopReading() {
Log.d(TAG, "stopReading");
synchronized (parcelFileDescriptorSemaphore) {
if (isAlive()) {
unplugConnection();
interrupt();
}
}
}
public void unplugConnection() {
Log.d(TAG, "unplugConnection");
synchronized (parcelFileDescriptorSemaphore) {
ParcelFileDescriptor oldFileDescriptor = this.parcelFileDescriptor;
this.parcelFileDescriptor = null;
if (oldFileDescriptor != null) {
try {
oldFileDescriptor.close();
} catch (IOException ex) {
// Ignore!
}
}
parcelFileDescriptorSemaphore.notifyAll();
}
}
public void replugConnection(ParcelFileDescriptor newFd) {
Log.d(TAG, "ParcelFileDescriptor");
if (this.parcelFileDescriptor != null) {
unplugConnection();
}
synchronized (parcelFileDescriptorSemaphore) {
this.parcelFileDescriptor = newFd;
parcelFileDescriptorSemaphore.notifyAll();
}
}
@Override
public void run() {
Log.d(TAG, "Reader thread was started.");
try {
for (;;) { // Forever: We'll be interrupted when we're supposed to stop!
try {
BlockingInputStream inputStream = waitForInputStream();
Log.d(TAG, "Reader thread has a connection. Reading...");
ensureCollectorIsBeingReset();
int read = inputStream.read();
while (read >= 0) {
sendByteToCollector(read);
read = inputStream.read();
}
Log.d(TAG, "Reader thread got an EOF!");
packetCollector.error(new EndOfFileException());
inputStream.close();
unplugConnection();
} catch (IOException ioe) {
Log.d(TAG, "Reader thread got an IOException: " + ioe);
packetCollector.error(ioe);
}
}
} catch (InterruptedException ie) {
Log.d(TAG, "Reader thread was interupted");
} finally {
Log.d(TAG, "Reader thread terminating!");
}
}
public void write(byte[] bytes) throws IOException {
synchronized (parcelFileDescriptorSemaphore) {
if (parcelFileDescriptor == null) {
throw new IOException("Missing parcelFileDescriptor - Bluetooth communication has been unplugged");
}
OutputStream outputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
try {
outputStream.write(bytes);
} finally {
outputStream.close();
}
}
}
private void ensureCollectorIsBeingReset() {
resetCollector = true;
}
private void sendByteToCollector(int read) {
long now = System.currentTimeMillis();
boolean withinTimeoutPeriod = now - resetTime < RESET_TIME;
if (withinTimeoutPeriod) {
resetTime = now;
} else {
if (resetCollector) {
packetCollector.reset();
resetCollector = false;
}
packetCollector.receive((byte) read);
}
}
private BlockingInputStream waitForInputStream() throws InterruptedException {
synchronized (parcelFileDescriptorSemaphore) {
Log.d(TAG, "Reader thread waiting for a connection.");
while (parcelFileDescriptor == null) {
parcelFileDescriptorSemaphore.wait();
}
return new BlockingInputStream(new FileInputStream(parcelFileDescriptor.getFileDescriptor()));
}
}
}