25

The application gives the users 2 connection options, USB and Bluetooth. USB works fine. I have obtained sample codes for Bluetooth connection, however they are designed to work as activity. I tried to do it in a Service but failed.

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
startService(new Intent(this, MyService.class));

<service android:enabled="true" android:name=".MyService" />
enter code here

I need to establish Bluetooth communication with a device that is already paired and its MAC address is known. So I can skip discovery and pairing phases. The device I am trying to connect is always discoverable and awaiting connection. Is there a way to do this from a Service Class and keep that connection up through other activities?

I am using BluetoothChatService and DeviceListActivity

wervdon
  • 557
  • 2
  • 13
  • 24

1 Answers1

38

I have written a Bluetooth services which runs in the background and can communicate to any Activity in the application using Messenger http://developer.android.com/guide/components/bound-services.html.

public class PrinterService extends Service {
private BluetoothAdapter mBluetoothAdapter;
public static final String BT_DEVICE = "btdevice";
public static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
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
private ConnectThread mConnectThread;
private static ConnectedThread mConnectedThread;
// public mInHangler mHandler = new mInHangler(this);
private static Handler mHandler = null;
public static int mState = STATE_NONE;
public static String deviceName;
public Vector<Byte> packdata = new Vector<Byte>(2048);
public static Device device = null;

@Override
public void onCreate() {
    Log.d("PrinterService", "Service started");
    super.onCreate();
}

@Override
public IBinder onBind(Intent intent) {
    mHandler = ((MyAplication) getApplication()).getHandler();
    return mBinder;
}

public class LocalBinder extends Binder {
    PrinterService getService() {
        return PrinterService.this;
    }
}



private final IBinder mBinder = new LocalBinder();

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d("PrinterService", "Onstart Command");
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter != null) {
        device = (Device) intent.getSerializableExtra(BT_DEVICE);
        deviceName = device.getDeviceName();
        String macAddress = device.getMacAddress();
        if (macAddress != null && macAddress.length() > 0) {
            connectToDevice(macAddress);
        } else {
            stopSelf();
            return 0;
        }
    }
    String stopservice = intent.getStringExtra("stopservice");
    if (stopservice != null && stopservice.length() > 0) {
        stop();
    }
    return START_STICKY;
}

private synchronized void connectToDevice(String macAddress) {
    BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddress);
    if (mState == STATE_CONNECTING) {
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
    }

    // Cancel any thread currently running a connection
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    mConnectThread = new ConnectThread(device);
    mConnectThread.start();
    setState(STATE_CONNECTING);
}

private void setState(int state) {
    PrinterService.mState = state;
    if (mHandler != null) {
        mHandler.obtainMessage(AbstractActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
    }
}

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

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

@Override
public boolean stopService(Intent name) {
    setState(STATE_NONE);
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }

    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }
    mBluetoothAdapter.cancelDiscovery();
    return super.stopService(name);
}

private void connectionFailed() {
    PrinterService.this.stop();
    Message msg = mHandler.obtainMessage(AbstractActivity.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(AbstractActivity.TOAST, getString(R.string.error_connect_failed));
    msg.setData(bundle);
    mHandler.sendMessage(msg);
}

private void connectionLost() {
    PrinterService.this.stop();
    Message msg = mHandler.obtainMessage(AbstractActivity.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(AbstractActivity.TOAST, getString(R.string.error_connect_lost));
    msg.setData(bundle);
    mHandler.sendMessage(msg);
}

private static Object obj = new Object();

public static void write(byte[] out) {
    // Create temporary object
    ConnectedThread r;
    // Synchronize a copy of the ConnectedThread
    synchronized (obj) {
        if (mState != STATE_CONNECTED)
            return;
        r = mConnectedThread;
    }
    // Perform the write unsynchronized
    r.write(out);
}

private synchronized void connected(BluetoothSocket mmSocket, BluetoothDevice mmDevice) {
    // Cancel the thread that completed the connection
    if (mConnectThread != null) {
        mConnectThread.cancel();
        mConnectThread = null;
    }

    // Cancel any thread currently running a connection
    if (mConnectedThread != null) {
        mConnectedThread.cancel();
        mConnectedThread = null;
    }

    mConnectedThread = new ConnectedThread(mmSocket);
    mConnectedThread.start();

    // Message msg =
    // mHandler.obtainMessage(AbstractActivity.MESSAGE_DEVICE_NAME);
    // Bundle bundle = new Bundle();
    // bundle.putString(AbstractActivity.DEVICE_NAME, "p25");
    // msg.setData(bundle);
    // mHandler.sendMessage(msg);
    setState(STATE_CONNECTED);

}

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

    public ConnectThread(BluetoothDevice device) {
        this.mmDevice = device;
        BluetoothSocket tmp = null;
        try {
            tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(SPP_UUID));
        } catch (IOException e) {
            e.printStackTrace();
        }
        mmSocket = tmp;
    }

    @Override
    public void run() {
        setName("ConnectThread");
        mBluetoothAdapter.cancelDiscovery();
        try {
            mmSocket.connect();
        } catch (IOException e) {
            try {
                mmSocket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            connectionFailed();
            return;

        }
        synchronized (PrinterService.this) {
            mConnectThread = null;
        }
        connected(mmSocket, mmDevice);
    }

    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
            Log.e("PrinterService", "close() of connect socket failed", e);
        }
    }
}

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 {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            Log.e("Printer Service", "temp sockets not created", e);
        }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    @Override
    public void run() {
        while (true) {
            try {
                if (!encodeData(mmInStream)) {
                    mState = STATE_NONE;
                    connectionLost();
                    break;
                } else {
                }
                // mHandler.obtainMessage(AbstractActivity.MESSAGE_READ,
                // bytes, -1, buffer).sendToTarget();
            } catch (Exception e) {
                e.printStackTrace();
                connectionLost();
                PrinterService.this.stop();
                break;
            }

        }
    }

    private byte[] btBuff;


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

            // Share the sent message back to the UI Activity
            mHandler.obtainMessage(AbstractActivity.MESSAGE_WRITE, buffer.length, -1, buffer).sendToTarget();
        } catch (IOException e) {
            Log.e("PrinterService", "Exception during write", e);
        }
    }

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

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

}

public void trace(String msg) {
    Log.d("AbstractActivity", msg);
    toast(msg);
}

public void toast(String msg) {
    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}

@Override
public void onDestroy() {
    stop();
    Log.d("Printer Service", "Destroyed");
    super.onDestroy();
}

private void sendMsg(int flag) {
    Message msg = new Message();
    msg.what = flag;
    handler.sendMessage(msg);
}

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {//
        if (!Thread.currentThread().isInterrupted()) {
            switch (msg.what) {
            case 3:

                break;

            case 4:

                break;
            case 5:
                break;

            case -1:
                break;
            }
        }
        super.handleMessage(msg);
    }

};
}

UPDATE

you need to use Handler in your MyApplication Class

Handler.Callback realCallback = null;
Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        if (realCallback != null) {
            realCallback.handleMessage(msg);
        }
    };
};
public Handler getHandler() {
    return handler;
}
public void setCallBack(Handler.Callback callback) {
    this.realCallback = callback;
}
Kapil Vats
  • 5,485
  • 1
  • 27
  • 30
  • 1
    can you link AbstractActivity Class too please? – wervdon Feb 25 '13 at 07:38
  • 2
    `AbstractActivity` is nothing but a base activity for all your activities, so just create a abstract classs which extends `Activity`, then in all your activity just extends your baseActivity instead of `Activity` – Kapil Vats Feb 25 '13 at 07:50
  • 1
    Cool, I already have such a class. Can you explain how you get Handler from MyApplication Class? `mHandler = ((MyAplication) getApplication()).getHandler();` I also have a MyApplication Class but don't know how to pass Handler to it. – wervdon Feb 25 '13 at 08:03
  • 1
    you need to define a handler in your MyApplication Class, and geter seter for that, I have updated my post – Kapil Vats Feb 25 '13 at 08:18
  • All my `Activity` Classes extend `BaseActivity`. `MyApplication` has a Handler with setters and getters. Added `PrinterService` in `AndroidManifest.xml`. `public static Device device = null; device = (Device) intent.getSerializableExtra(BT_DEVICE); deviceName = device.getDeviceName(); String macAddress = device.getMacAddress();` getDeviceName() and getMacAddress() is undefined, so I changed it to: `public static BluetoothDevice device = null; device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String macAddress = device.getAddress();` getting NullPointerException – wervdon Feb 25 '13 at 09:44
  • also tried: `public static BluetoothDevice device = null; String macAddress = intent.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddress);` NullPointerException again – wervdon Feb 25 '13 at 09:45
  • hmm, while starting the service you need to put extra parameters in the 'EXTRA_DEVICE_ADDRESS' as mac address – Kapil Vats Feb 25 '13 at 10:01
  • I have been modifying and trying it for a while, it's working fine. Thank you – wervdon Mar 07 '13 at 07:42
  • @KapilVats : Your code worked for me.However,I see that the connection gets disconnected after about 10-15 mins.Have you faced the same in your testing as well? How can I sustain the connection ideally forever(assuming both devices are in range and have power supply) ? – Basher51 Jul 23 '14 at 14:15
  • @Basher51 For me It was working fine, you can find out in your logcat, why your device is disconnecting. – Kapil Vats Jul 24 '14 at 06:12
  • @KapilVats : I am testing since yesterday and found that at times the connections were sustaining and on other times it stopped(may be due to some issue in my code/when I simulated BT failures between the devices).Does your code handle the retry cases,say when the BT gets disconnected between the device and BT module for some reason? – Basher51 Jul 24 '14 at 07:59
  • @KapilVats :Also,does the above code check/handle race conditions,if any? – Basher51 Jul 24 '14 at 08:22
  • @KapilVats: What about the retry case.Suppose during the course of operation the BT connection fails between the device and BT module,will the threads in the service re-connect the connection?From what I see from the above code,its pretty much a top down approach, or am I missing something? – Basher51 Jul 25 '14 at 10:17
  • 1
    what `MyAplication` sould contain? Or what class is it, the one that starts the service? Thanks – Iosif Apr 14 '15 at 12:11
  • @losif `MyApplication` class is any singeton class, it can be subclass of Android `Application'. – Kapil Vats Apr 16 '15 at 11:06
  • Could anyone point to a running and complete source code of this? – Anthea Jan 12 '16 at 11:26
  • @KapilVats: Your answer has sparked [a new question](http://stackoverflow.com/questions/36930499/can-anyone-explain-how-to-manage-messages), if you are interested to provide some input. – user000001 Apr 29 '16 at 07:03
  • @user000001 Please read the documentation http://developer.android.com/guide/components/bound-services.html – Kapil Vats Apr 29 '16 at 08:23
  • @KapilVats: It's not my question, but ok. – user000001 Apr 29 '16 at 08:25
  • @KapilVats: Could you provide a full source code for above question (for example in github). Many peoples (include me) want to run it – Jame Aug 11 '16 at 08:07
  • @KapilVats Forgive my ignorance but what is encodeData ? – Sebastien FERRAND Oct 18 '17 at 05:36
  • @KapilVats I have implemented the same in my project, but while connecting a socket (mmSocket.connect()) I'm getting IOException "read failed, socket might closed or timeout, read ret: -1". I'm testing on android version L and M. – Vaibhav Jadhav May 30 '19 at 08:44
  • For anyone trying to write this in kotlin and having trouble writing the handler, see this: https://stackoverflow.com/questions/52025220/how-to-use-handler-and-handlemessage-in-kotlin/52025302 – jacob Dec 31 '19 at 03:13
  • anyway, what is encodeData ? literally, i can;t found the function have declared as encodeData in this solution – Michael Fernando Jul 03 '23 at 03:18