/** @file SyncService.java
*
* --------------------------------------------------------
* this class is made after the sample BluetoothChat by the
* The Android Open Source Project which is licenced under
* the Apache License, Version 2.0 (the "License");
* --------------------------------------------------------
*/
package com.topodroid.DistoX;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class SyncService
{
// Name for the SDP record when creating server socket
private static final String NAME = "TopoDroidSync";
// Unique UUID for this application
private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
// Member fields
// private Context mContext;
private TopoDroidApp mApp;
private final BluetoothAdapter mAdapter;
private BluetoothDevice mRemoteDevice;
private final Handler mHandler;
private AcceptThread mAcceptThread;
private ConnectingThread mConnectingThread;
private ConnectedThread mConnectedThread;
private int mConnectState; // NONE --> CONNECTING --> CONNECTED --> NONE
private int mAcceptState; // NONE --> LISTEN --> NONE
private int mType; // the service type. either server (LISTEN) or client (CONNECTING)
private boolean mConnectRun;
private boolean mAcceptRun;
// Constants that indicate the current connection state
public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device
static final String mStateName[] = { "none", "listen", "connecting", "connected" };
static final int MESSAGE_CONNECT_STATE = 1;
static final int MESSAGE_DEVICE = 2;
static final int MESSAGE_READ = 3;
static final int MESSAGE_WRITE = 4;
static final int MESSAGE_LOST_CONN = 5;
static final int MESSAGE_FAIL_CONN = 6;
static final int MESSAGE_ACCEPT_STATE = 7;
static final String DEVICE = "DEVICE";
public SyncService( /* Context context, */ TopoDroidApp app, Handler handler )
{
// mContext = context;
mApp = app;
mAdapter = BluetoothAdapter.getDefaultAdapter();
mRemoteDevice = null;
mConnectState = STATE_NONE;
mAcceptState = STATE_NONE;
mType = STATE_NONE;
mHandler = handler;
mConnectRun = false;
mAcceptRun = false;
}
private synchronized void setConnectState(int state)
{
TDLog.Log( TDLog.LOG_SYNC, "sync connect state: "
+ mStateName[mConnectState] + " --> " + mStateName[state] );
// if ( state == STATE_NONE ) mRemoteDevice = null;
mConnectState = state;
mHandler.obtainMessage( MESSAGE_CONNECT_STATE, state, -1).sendToTarget();
}
private synchronized void setAcceptState(int state)
{
TDLog.Log( TDLog.LOG_SYNC, "sync accept state "
+ mStateName[mAcceptState] + " --> " + mStateName[state] );
mAcceptState = state;
mHandler.obtainMessage( MESSAGE_ACCEPT_STATE, state, -1).sendToTarget();
}
public synchronized int getConnectState() { return mConnectState; }
public synchronized int getAcceptState() { return mAcceptState; }
String getConnectStateStr()
{
switch ( mConnectState ) {
case STATE_NONE: return "NONE";
// case STATE_LISTEN: return "LISTEN";
case STATE_CONNECTING: return "CONNECTING";
case STATE_CONNECTED: return "CONNECTED " + mRemoteDevice.getName();
}
return "UNKNOWN";
}
String getConnectedDeviceName()
{
return ( mRemoteDevice != null )? mRemoteDevice.getName() : null;
}
public int getType() { return mType; }
public synchronized void start()
{
TDLog.Log( TDLog.LOG_SYNC, "sync start()" );
mAcceptRun = false;
mConnectRun = false;
if (mConnectingThread != null) { mConnectingThread.cancel(); mConnectingThread = null; }
if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; }
startAccept();
}
private synchronized void startAccept()
{
TDLog.Log( TDLog.LOG_SYNC, "sync startAccept()" );
if (mAcceptThread != null) {
mAcceptRun = false;
setAcceptState(STATE_NONE);
try {
mAcceptThread.join();
} catch ( InterruptedException e ) { }
}
mAcceptRun = true;
mType = STATE_LISTEN;
mAcceptThread = new AcceptThread();
mAcceptThread.start();
setAcceptState(STATE_LISTEN);
}
/**
* Start the ConnectingThread to initiate a connection to a remote device.
* @param device The BluetoothDevice to connect
*/
public synchronized void connect( BluetoothDevice device )
{
TDLog.Log( TDLog.LOG_SYNC, "sync connect to " + device.getName() );
mRemoteDevice = device;
mConnectRun = false;
if ( mConnectState == STATE_CONNECTING && mConnectingThread != null ) {
mConnectingThread.cancel();
mConnectingThread = null;
}
if ( mConnectedThread != null ) { mConnectedThread.cancel(); mConnectedThread = null; }
reconnect();
}
private synchronized void reconnect()
{
if ( mRemoteDevice == null ) return;
TDLog.Log( TDLog.LOG_SYNC, "sync reconnect to " + mRemoteDevice.getName() );
mType = STATE_CONNECTING;
mConnectRun = true;
mConnectingThread = new ConnectingThread( mRemoteDevice );
mConnectingThread.start();
setConnectState(STATE_CONNECTING);
}
/**
* Start the ConnectedThread to begin managing a Bluetooth connection
* @param socket The BluetoothSocket on which the connection was made
* @param device The BluetoothDevice that has been connected
*/
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device)
{
TDLog.Log( TDLog.LOG_SYNC, "sync connected. remote device " + device.getName() );
mRemoteDevice = device;
mConnectRun = true;
if ( mConnectingThread != null ) { mConnectingThread.cancel(); mConnectingThread = null; }
if ( mConnectedThread != null ) { mConnectedThread.cancel(); mConnectedThread = null; }
// ONE-TO-ONE
// if ( mAcceptThread != null ) { mAcceptThread.cancel(); mAcceptThread = null; }
mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
Message msg = mHandler.obtainMessage( MESSAGE_DEVICE );
Bundle bundle = new Bundle();
bundle.putString( DEVICE, mRemoteDevice.getName() );
msg.setData(bundle);
mHandler.sendMessage(msg);
setConnectState(STATE_CONNECTED);
}
public synchronized void disconnect()
{
TDLog.Log( TDLog.LOG_SYNC, "sync disconnect");
if ( mConnectingThread != null ) { mConnectingThread.cancel(); mConnectingThread = null; }
if ( mConnectState == STATE_CONNECTED ) {
byte shutdown[] = new byte[4];
shutdown[0] = 0;
shutdown[1] = DataListener.SHUTDOWN;
shutdown[2] = 0;
shutdown[3] = DataListener.EOL;
writeBuffer( shutdown ); // FIXME if failure ? nothing: connectedThread already closed
}
if ( mConnectedThread != null ) { mConnectedThread.cancel(); mConnectedThread = null; }
mRemoteDevice = null;
setConnectState( STATE_NONE );
}
public synchronized void stop()
{
TDLog.Log( TDLog.LOG_SYNC, "sync stop");
if ( mAcceptThread != null ) { mAcceptThread.cancel(); mAcceptThread = null; }
setAcceptState( STATE_NONE );
mType = STATE_NONE;
}
public boolean writeBuffer( byte[] buffer )
{
// Log.v("DistoX", "sync write (conn state " + mConnectState + " length " + buffer.length + ") " + buffer[0] + " " + buffer[1] + " ... ");
ConnectedThread r; // Create temporary object
synchronized (this) { // Synchronize a copy of the ConnectedThread
if ( mConnectState != STATE_CONNECTED ) return false;
r = mConnectedThread;
}
if ( r.doWriteBuffer( buffer ) ) { // Perform the write unsynchronized
return true;
} // else {
mConnectedThread.cancel();
mConnectedThread = null;
mRemoteDevice = null;
setConnectState( STATE_NONE );
return false;
}
// called by the Connect-Thread
private void connectionFailed()
{
TDLog.Error( "sync connection failed");
mRemoteDevice = null;
setConnectState(STATE_NONE);
mType = STATE_NONE;
Message msg = mHandler.obtainMessage( MESSAGE_FAIL_CONN );
mHandler.sendMessage(msg);
}
// called by the connected-Thread
private void connectionLost()
{
TDLog.Error( "sync connection lost");
mRemoteDevice = null;
setConnectState(STATE_NONE);
Message msg = mHandler.obtainMessage( MESSAGE_LOST_CONN );
mHandler.sendMessage(msg);
}
/**
* This thread runs while listening for incoming connections. It behaves
* like a server-side client. It runs until a connection is accepted
* (or until cancelled).
*/
private class AcceptThread extends Thread
{
private BluetoothServerSocket mmServerSocket;
public AcceptThread() {
createServerSocket();
}
private void createServerSocket()
{
BluetoothServerSocket tmp = null;
try { // Create a new listening server socket
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
TDLog.Error( "listen() failed " + e);
}
mmServerSocket = tmp;
}
public void run()
{
TDLog.Log( TDLog.LOG_SYNC, "sync AcceptThread run");
setName("AcceptThread");
BluetoothSocket socket = null;
while ( mAcceptRun && mAcceptState == STATE_LISTEN ) {
if ( mmServerSocket == null ) { // FIXME should not happen
TDLog.Error("null server socket");
mConnectState = STATE_NONE;
break;
}
try {
// Log.v("DistoX", "sync accept listening ... ");
socket = mmServerSocket.accept(); // blocking call
} catch (IOException e) {
TDLog.Error( "accept() failed " + e);
break;
}
if (socket != null) { // If a connection was accepted
TDLog.Log(TDLog.LOG_SYNC, "incoming connection request " + socket.getRemoteDevice().getName() );
synchronized ( SyncService.this ) {
switch ( mConnectState ) {
case STATE_NONE:
case STATE_CONNECTING: // Situation normal. Start the connected thread.
connected( socket, socket.getRemoteDevice() );
break;
case STATE_CONNECTED: // Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
TDLog.Error( "Could not close unwanted socket " + e);
}
break;
}
}
// createServerSocket();
socket = null;
}
}
TDLog.Log( TDLog.LOG_SYNC, "sync AcceptThread done");
}
public void cancel()
{
try {
mAcceptRun = false;
mmServerSocket.close();
} catch (IOException e) {
TDLog.Error( "close() of server failed " + e);
}
}
}
/**
* This thread runs while attempting to make an outgoing connection
* with a device. It runs straight through; the connection either
* succeeds or fails.
*/
private class ConnectingThread extends Thread
{
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectingThread( BluetoothDevice device )
{
mmDevice = device;
BluetoothSocket tmp = null;
// Get a BluetoothSocket for a connection with the given BluetoothDevice
try {
// Class[] classes1 = new Class[ 1 ];
// classes1[0] = int.class;
// Method m = mmDevice.getClass().getMethod( "createInsecureRfcommSocket", classes1 );
// tmp = (BluetoothSocket) m.invoke( mBTDevice, 1 );
//
tmp = mmDevice.createRfcommSocketToServiceRecord( MY_UUID );
} catch (IOException e) {
TDLog.Error( "ConnectingThread cstr failed " + e);
}
mmSocket = tmp;
}
public void run()
{
TDLog.Log( TDLog.LOG_SYNC, "sync ConnectingThread run");
setName("ConnectingThread");
mAdapter.cancelDiscovery(); // Always cancel discovery because it will slow down a connection
try { // Make a connection to the BluetoothSocket
mmSocket.connect(); // blocking call
} catch (IOException e) {
connectionFailed();
try { // Close the socket
mmSocket.close();
} catch (IOException e2) {
TDLog.Error( "unable to close() socket during connection failure " + e2);
}
// SyncService.this.start(); // Start the service over to restart listening mode
return;
}
synchronized ( SyncService.this ) { // Reset the ConnectingThread because we're done
mConnectingThread = null;
}
connected( mmSocket, mmDevice ); // Start the connected thread
TDLog.Log( TDLog.LOG_SYNC, "sync connecting thread done");
}
public void cancel()
{
try {
mmSocket.close();
} catch (IOException e) {
TDLog.Error( "close() of connect socket failed " + e);
}
}
}
/**
* This thread runs during a connection with a remote device.
* It handles all incoming and outgoing transmissions.
*/
private class ConnectedThread extends Thread
{
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket)
{
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try { // Get the BluetoothSocket input and output streams
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
TDLog.Error( "temp sockets not created " + e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run()
{
TDLog.Log( TDLog.LOG_SYNC, "sync connected thread run");
byte[] buffer = new byte[512];
byte[] data = new byte[4096];
int bytes;
int pos = 0; // data pos
while ( mConnectRun ) { // Keep listening to the InputStream while connected
try {
bytes = mmInStream.read(buffer); // Read from the InputStream
for ( int k=0; k<bytes; ++k ) {
// add buffer to the data
if ( buffer[k] == DataListener.EOL ) {
// end of message: send to upper layer
byte[] tmp = new byte[pos];
for ( int j=0; j<pos; ++j) tmp[j] = data[j];
// special handle shutdown message
if ( data[0] == 0 &&
data[1] == DataListener.SHUTDOWN &&
data[2] == 0 ) {
mConnectRun = false;
mRemoteDevice = null;
setConnectState( STATE_NONE );
} else {
// Log.v("DistoX", "read <" + data[0] + "|" + data[1] + ">" );
mHandler.obtainMessage( MESSAGE_READ, pos, -1, tmp).sendToTarget();
pos = 0;
}
} else {
data[pos] = buffer[k];
++pos;
}
}
} catch (IOException e) {
TDLog.Error( "disconnected " + e);
try { // Close the socket
mmSocket.close();
} catch (IOException e2) {
TDLog.Error( "unable to close() socket during connection failure " + e2);
}
connectionLost();
break;
}
}
// TopoDroidLoLogog( TDLog.LOG_SYNC, "sync ConnectedThread done type " + mType );
if ( mType == STATE_LISTEN ) {
// ONE-TO-ONE
// startAccept();
} else if ( mType == STATE_CONNECTING ) {
try {
Thread.sleep( 200 );
} catch ( InterruptedException e ) { }
reconnect();
}
}
/**
* Write to the connected OutStream.
* @param buffer The bytes to write
*/
public boolean doWriteBuffer( byte[] buffer )
{
Log.v("DistoX", "sync connected write " + buffer.length + ": <" + buffer[0] + "|" + buffer[1] + ">" );
try {
mmOutStream.write( buffer );
// Share the sent message back to the UI Activity: NOT USED .... FIXME
// mHandler.obtainMessage( MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
} catch (IOException e) {
TDLog.Error( "Exception during write " + e );
return false;
}
return true;
}
public void cancel()
{
mConnectRun = false;
try {
mmInStream.close();
mmSocket.close();
} catch (IOException e) {
TDLog.Error( "close() of connect socket failed " + e );
}
}
}
}