package eu.hgross.blaubot.android.bluetooth;
import android.bluetooth.BluetoothSocket;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
import eu.hgross.blaubot.core.AbstractBlaubotConnection;
import eu.hgross.blaubot.core.IBlaubotConnection;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.util.Log;
/**
* An android specific {@link IBlaubotConnection} implementation for bluetooth.
*
* @author Henning Gross {@literal (mail.to@henning-gross.de)}
*
*/
public class BlaubotBluetoothConnection extends AbstractBlaubotConnection {
private static final String LOG_TAG = "BlaubotBluetoothConnection";
private BluetoothSocket socket;
private IBlaubotDevice bluetoothDevice;
private DataInputStream dataInputStream;
private UUID uuid = UUID.randomUUID(); // for instance based hashcode/equals
public BlaubotBluetoothConnection(IBlaubotDevice device, BluetoothSocket socket) {
this.bluetoothDevice = device;
this.socket = socket;
try {
this.dataInputStream = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
if(Log.logErrorMessages()) {
Log.e(LOG_TAG, "Failed to get InputStream", e);
}
throw new RuntimeException("Failed to get InputStream");
}
}
private boolean notifiedDisconnect = false;
@Override
protected void notifyDisconnected() {
if(notifiedDisconnect)
return;
super.notifyDisconnected();
notifiedDisconnect = true;
}
private Object closeMonitor = new Object();
private boolean disconnected = false;
@Override
public void disconnect() {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Disconnecting BluetoothBlaubotConnection " + this + " ...");
}
if(!disconnected) {
try {
synchronized (closeMonitor) {
socket.close();
disconnected = true;
}
} catch (IOException e) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Failed to close socket", e);
}
}
}
this.notifyDisconnected();
}
private void handleSocketException(IOException e) throws IOException {
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "Got socket exception", e);
}
this.disconnect();
throw e;
}
@Override
public int read() throws IOException {
checkClosed();
try {
return this.socket.getInputStream().read();
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
/**
* checks if the socket was disconnected and throws an IOException if so
* @throws IOException
*/
private void checkClosed() throws IOException {
// some device have problems when using a bluetooth socket after it was closed: http://stackoverflow.com/questions/18147925/fatal-signal-11-sigsegv-when-closing-bluetoothsocket-on-android-4-2-2-and-4-3/29529974#2952997
// fix: after disconnect() store a flag and if set, always throw IOException when trying to read/write
synchronized (closeMonitor) {
if (disconnected) {
throw new IOException("Connection closed");
}
}
}
@Override
public int read(byte[] b) throws IOException {
checkClosed();
try {
return this.socket.getInputStream().read(b);
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
checkClosed();
try {
return this.socket.getInputStream().read(buffer, byteOffset, byteCount);
} catch (IOException e) {
this.handleSocketException(e);
return -1; // will never get here
}
}
@Override
public void write(int b) throws IOException {
checkClosed();
try {
this.socket.getOutputStream().write(b);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void write(byte[] bytes) throws IOException {
checkClosed();
try {
this.socket.getOutputStream().write(bytes);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
checkClosed();
try {
this.socket.getOutputStream().write(b,off,len);
} catch (IOException e) {
this.handleSocketException(e);
}
}
@Override
public void readFully(byte[] buffer) throws IOException {
checkClosed();
try {
dataInputStream.readFully(buffer);
} catch (IOException e) {
handleSocketException(e);
}
}
@Override
public void readFully(byte[] buffer, int offset, int byteCount) throws IOException {
checkClosed();
try {
dataInputStream.readFully(buffer, offset, byteCount);
} catch (IOException e) {
handleSocketException(e);
}
}
@Override
public IBlaubotDevice getRemoteDevice() {
return this.bluetoothDevice;
}
@Override
public boolean isConnected() {
return this.socket.isConnected();
}
@Override
public String toString() {
return "BlaubotBluetoothConnection["+getRemoteDevice()+"]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
BlaubotBluetoothConnection that = (BlaubotBluetoothConnection) o;
if (bluetoothDevice != null ? !bluetoothDevice.equals(that.bluetoothDevice) : that.bluetoothDevice != null)
return false;
if (socket != null ? !socket.equals(that.socket) : that.socket != null) return false;
if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (socket != null ? socket.hashCode() : 0);
result = 31 * result + (bluetoothDevice != null ? bluetoothDevice.hashCode() : 0);
result = 31 * result + (uuid != null ? uuid.hashCode() : 0);
return result;
}
}