Android reconnect to bluetooth device if connection lost

Tadej Vengust picture Tadej Vengust · May 6, 2015 · Viewed 12k times · Source

My phone is connecting to a bluetooth device and it works normally. It connect and the connection holds. I can change orientation, have my app in the background, I can close app and when I turn it back on it will automatically connect. But from app users I get some reports that the connection is lost after a period of time(no pattern detected). I have tried to recreate this but with no luck. So to avoid this problem I want to implement an automatic reconnect to the previously selected device if connection is lost.

I did some reasearch that this could be done by implementing a broadcast reciever that would detect: android.bluetooth.device.action.ACL_DISCONNECTED

What my questions are:

  • I already have a broadcast reciever that is triggered on device bootup. Should I extend this reciever to add the ACL_DISCONNECTED or add a separate reciever?
  • My BluetoothService is initialised on my mainactivity, but I will need to reconnect from the intent. How to do that.

Thanks for your answer.

BluetoothServis code:

public class BluetoothService
{
    // Debugging
    private static final String TAG = "BluetoothService";    
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

// Member fields
private final BluetoothAdapter mAdapter;
private final Handler mHandler;
private final Context mContext;
private ConnectThread mConnectThread;
private ConnectedThread mConnectedThread;    
private int mState;

// Constants that indicate the current connection state
public static final int STATE_NONE = 0;       // we're doing nothing
public static final int STATE_CONNECTING = 1; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 2;  // now connected to a remote device

public BluetoothService(Context context, Handler handler)
{
    mAdapter = BluetoothAdapter.getDefaultAdapter();
    mState = STATE_NONE;
    mHandler = handler;
    mContext = context;
}

private synchronized void setState(int state)
{
    mState = state;
    mHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
}

public synchronized int getState()
{
    return mState;
}

public synchronized void start()
{
    if (mConnectThread != null) 
    {
        mConnectThread.cancel(); 
        mConnectThread = null;
    }

    if (mConnectedThread != null)
    {
        mConnectedThread.cancel(); 
        mConnectedThread = null;
    }
}

public synchronized void connect(BluetoothDevice device)
{
    if (mState == STATE_CONNECTING)
    {
        if (mConnectThread != null) 
        {
            mConnectThread.cancel(); 
            mConnectThread = null;
        }
    }

    if (mConnectedThread != null) 
    {
        mConnectedThread.cancel(); 
        mConnectedThread = null;
    }

    mConnectThread = new ConnectThread(device);
    mConnectThread.start();


    setState(STATE_CONNECTING);
}

public synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType)
{
    if (mConnectThread != null)
    {
        mConnectThread.cancel();
        mConnectThread = null;
    }

    if (mConnectedThread != null)
    {
        mConnectedThread.cancel(); 
        mConnectedThread = null;
    }

    mConnectedThread = new ConnectedThread(socket, socketType);
    mConnectedThread.start();

    Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME);
    Bundle bundle = new Bundle();
    bundle.putString(MainActivity.DEVICE_NAME, device.getName());
    msg.setData(bundle);
    mHandler.sendMessage(msg);

    setState(STATE_CONNECTED);
}

public synchronized void stop()
{
    if (mConnectThread != null)
    {
        mConnectThread.cancel();
        mConnectThread = null;
    }

    if (mConnectedThread != null)
    {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }

    setState(STATE_NONE);
}

public void write(byte[] out)
{
    ConnectedThread r;
    synchronized (this)
    {
        if (mState != STATE_CONNECTED) return;
        r = mConnectedThread;
    }
    r.write(out);
}

private void connectionFailed()
{
    Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(MainActivity.TOAST, mContext.getResources().getString(R.string.cant_connect));
    msg.setData(bundle);
    mHandler.sendMessage(msg);

    BluetoothService.this.start();
}  

private class ConnectThread extends Thread
{
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    private String mSocketType;

    public ConnectThread(BluetoothDevice device)
    {
        mmDevice = device;
        BluetoothSocket tmp = null;
        mSocketType = "Secure";  

        // another option createInsecureRfcommSocketToServiceRecord
        try
        {
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } 

        catch (IOException e)
        {
            Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
        }

        /*
        Method m = null;
        try {
            m = mmDevice.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
            tmp = (BluetoothSocket) m.invoke(mmDevice, 1);

        } catch (Exception e) {

            e.printStackTrace();
        }*/


        mmSocket = tmp;            
    }

    public void run()
    {
        Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
        setName("ConnectThread" + mSocketType);

        mAdapter.cancelDiscovery();

        try
        {
            mmSocket.connect();
            Log.i(TAG, "ConnectThread running");
        } 

        catch (IOException e)
        {
            try
            {
                mmSocket.close();
            } 

            catch (IOException e2)
            {
                Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2);
            }

            connectionFailed();
            return;
        }

        synchronized (BluetoothService.this)
        {
            mConnectThread = null;
        }

        connected(mmSocket, mmDevice, mSocketType);
    }

    public void cancel()
    {
        try
        {
            mmSocket.close();
        }

        catch (IOException e)
        {
            Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
        }
    }
}

private class ConnectedThread extends Thread
{
    private final BluetoothSocket mmSocket;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket, String socketType)
    {
        mmSocket = socket;
        OutputStream tmpOut = null;

        try
        {
            tmpOut = socket.getOutputStream();
        } 

        catch (IOException e)
        {
            Log.e(TAG, "temp sockets not created", e);
        }

        mmOutStream = tmpOut;
    }

    public void run()
    {

    }

    public void write(byte[] buffer)
    {
        try
        {
            mmOutStream.write(buffer);

            try
            {
                sleep(2000);
            } 

            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        } 

        catch (IOException e)
        {
            Log.e(TAG, "Exception during write", e);
        }
    }

    public void cancel()
    {
        try
        {
            mmSocket.close();
        } 

        catch (IOException e)
        {
            Log.e(TAG, "close() of connect socket failed", e);
        }
    }
}

}

Answer

Tadej Vengust picture Tadej Vengust · May 6, 2015

I solved this in 2 stages. First I created a new broadcast reciever because my existing one was for ACTION_BOOT_COMPLETED and had to be declared in the android manifest. The one I created had to be dynamic because I needed an instance of MainActivity:

 if (bluetoothLostReceiver == null)
        {
            bluetoothLostReceiver = new BluetoothLostReceiver();
            bluetoothLostReceiver.setMainActivity(this);
            IntentFilter filter = new IntentFilter("android.bluetooth.device.action.ACL_DISCONNECTED");
            registerReceiver(bluetoothLostReceiver, filter);
        }

And in my reciever I tried a new connect to the printer. I added an additional paramenter tryBluetoothReconnect so that if I wanted to close the connection it wouldn't try to reconnect.

public class BluetoothLostReceiver extends BroadcastReceiver {

    MainActivity main = null;

    public void setMainActivity(MainActivity main)
    {
        this.main = main;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        if(BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction()) && Global.tryBluetoothReconnect)
        {
            if(Global.PosPrinterDeviceName != null && !Global.PosPrinterDeviceName.equals(""))
                main.connectPrinter(Global.PosPrinterDeviceName);
        }else
        {
            Global.tryBluetoothReconnect = true;
        }
    }
}

I also upgraded my Bluetooth service so that if connection was not successful (connectionFailed() method) it would try 4 more times:

    if(tryCount >= 4)
    {
        tryCount = 0;

        Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.TOAST, mContext.getResources().getString(R.string.cant_connect));
        msg.setData(bundle);
        mHandler.sendMessage(msg);

        BluetoothService.this.start();
    }
    else
    {
        tryCount++;
        connect(bluetoothDevice);
    }

Hope this helps anyone with the same problem.