package eu.hgross.blaubot.android.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import java.io.DataInputStream; import java.io.IOException; import java.util.UUID; import eu.hgross.blaubot.core.BlaubotConstants; import eu.hgross.blaubot.core.IBlaubotAdapter; import eu.hgross.blaubot.core.BlaubotConnectionManager; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor; import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener; import eu.hgross.blaubot.core.acceptor.IBlaubotListeningStateListener; import eu.hgross.blaubot.core.acceptor.UniqueDeviceIdHelper; import eu.hgross.blaubot.core.acceptor.discovery.BeaconMessage; import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore; import eu.hgross.blaubot.util.Log; /** * An Acceptor handling incoming bluetooth connections for android devices. * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ public class BlaubotBluetoothConnectionAcceptor implements IBlaubotConnectionAcceptor { private static final String LOG_TAG = BlaubotBluetoothConnectionAcceptor.class.toString(); private IBlaubotListeningStateListener listeningStateListener; private IBlaubotIncomingConnectionListener acceptorListener; private BluetoothAcceptThread acceptThread = null; private boolean started = false; private BlaubotBluetoothAdapter blaubotBluetoothAdapter; private IBlaubotBeaconStore beaconStore; public BlaubotBluetoothConnectionAcceptor(BlaubotBluetoothAdapter blaubotBluetoothAdapter) { this.blaubotBluetoothAdapter = blaubotBluetoothAdapter; } @Override public IBlaubotAdapter getAdapter() { return blaubotBluetoothAdapter; } @Override public void setBeaconStore(IBlaubotBeaconStore beaconStore) { this.beaconStore = beaconStore; } @Override public void startListening() { if (acceptThread != null) { stopListening(); } acceptThread = new BluetoothAcceptThread(blaubotBluetoothAdapter.getUUIDSet().getAppUUID()); acceptThread.start(); } @Override public void stopListening() { if(Log.logDebugMessages()) { Log.d(LOG_TAG, "Stop listening for bluetooth clients ..."); } if (acceptThread != null) { if(Log.logDebugMessages()) { Log.d(LOG_TAG, "Interrupting and joining acceptThread ..."); } acceptThread.interrupt(); try { acceptThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } if(Log.logDebugMessages()) { Log.d(LOG_TAG, "AcceptThread stopped ..."); } } acceptThread = null; } @Override public void setListeningStateListener(IBlaubotListeningStateListener stateListener) { this.listeningStateListener = stateListener; } @Override public void setAcceptorListener(IBlaubotIncomingConnectionListener acceptorListener) { this.acceptorListener = acceptorListener; } @Override public ConnectionMetaDataDTO getConnectionMetaData() { final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothConnectionMetaDataDTO connectionMetaDataDTO = new BluetoothConnectionMetaDataDTO(bluetoothAdapter.getAddress()); return connectionMetaDataDTO; } @Override public boolean isStarted() { return started; } /** * Handles initial BlauBot instance communication. Once a client connects, the connected socket is handed over to the {@link BlaubotConnectionManager} clientConnections * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class BluetoothAcceptThread extends Thread { private final String LOG_TAG = "BluetoothAcceptor.BluetoothAcceptThread"; private BluetoothServerSocket serverSocket; private UUID uuid; /** * @param uuid The uuid to register with bluetooth SDP */ public BluetoothAcceptThread(UUID uuid) { this.uuid = uuid; setName("bluetooth-acceptor-accept-thread"); } @Override public void interrupt() { // TODO move serversocket close to another method (semantics) super.interrupt(); if(this.serverSocket == null) { return; } Log.d(LOG_TAG, "Closing ServerSocket ..."); // TODO: first interrupt and close after a timeout if not interrupted until then try { this.serverSocket.close(); } catch (IOException e) { Log.e(LOG_TAG, "Closing ServerSocket caused exception", e); } } @Override public void run() { started = true; if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Accept Thread starting ..."); } BluetoothServerSocket s = null; try { s = BluetoothAdapter.getDefaultAdapter().listenUsingRfcommWithServiceRecord(BlaubotConstants.BLUETOOTH_ACCEPTORS_RFCOMM_SDP_SERVICE_NAME, this.uuid); serverSocket = s; } catch (IOException e) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Could not listen to RFCOMM", e); } started = false; throw new RuntimeException("TODO: handle listen() failure"); } boolean notifiedListening = false; while (!this.isInterrupted()) { BluetoothSocket socket = null; try { if(Log.logDebugMessages()) { Log.d(LOG_TAG, "Creating bluetooth ServerSocket for incoming BlueBot slaves ..."); } if(!notifiedListening && listeningStateListener!= null) { notifiedListening = true; listeningStateListener.onListeningStarted(BlaubotBluetoothConnectionAcceptor.this); } socket = serverSocket.accept(); // TODO: check out if interrupt() causes an InterruptedException } catch (IOException e) { if(Log.logWarningMessages()) { Log.w(LOG_TAG, "ServerSocket accept failed.", e); } } if (socket != null) { // we got a connection gather unique id String uniqueDeviceId; try { // read the accepting device's uniqueDeviceId DataInputStream dis = new DataInputStream(socket.getInputStream()); uniqueDeviceId = UniqueDeviceIdHelper.readUniqueDeviceId(dis); } catch (IOException e) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Something went wrong gathering the unique device id"); } try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } continue; } BlaubotBluetoothDevice device = new BlaubotBluetoothDevice(uniqueDeviceId, socket.getRemoteDevice()); if(Log.logDebugMessages()) { Log.d(LOG_TAG, "Got connection from blaubot slave device " + socket.getRemoteDevice().getAddress() + "(" + device.getName() + ")"); } BlaubotBluetoothConnection connection = new BlaubotBluetoothConnection(device, socket); // retrieve their beacon message with their state and most importantly their acceptor meta data final BeaconMessage theirBeaconMessage = BeaconMessage.fromBlaubotConnection(connection); beaconStore.putDiscoveryEvent(theirBeaconMessage, device); if(acceptorListener != null) { acceptorListener.onConnectionEstablished(connection); } else { if(Log.logWarningMessages()) { Log.w(LOG_TAG, "No AcceptorListener registered to " + this + " - connection established but unknown to everyone!"); } } } else { // TODO: we also end here, if we hit the limit of the max bluetooth connections on a device!! if(Log.logWarningMessages()) { Log.w(LOG_TAG, "Socket null - no client connected. This can happen if you hit the maximum connections supported by a device's bluetooth hardware, on timeout or aborted calls."); } } } // loop finished, check wether we need to notified observers, that we started listening // if so, notify that we are now not listening anymore if(notifiedListening && listeningStateListener != null) { listeningStateListener.onListeningStopped(BlaubotBluetoothConnectionAcceptor.this); } started = false; if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Accept Thread finished ..."); } } } }