Friday 1 January 2016

Bluetooth Communication

Bluetooth enables point to point & multipoint communication between bluetooth enabled devices. Android framework includes APIs for connection with other bluetooth enabled device and exchange data with connected device.

Android framework supports both Classic as well as Low Energy bluetooth and provides APIs for both. Classic bluetooth is used in app when both devices share a connection for longer periods & have large data to exchange. It drains the device battery being responsible for such intensive tasks. Low Energy bluetooth as name suggests consumes less power unlike classic bluetooth . It's application areas are very specific which suit intermittent burst data transfers. 

We will first cover classic bluetooth communication APIs and then low energy bluetooth communication.

In a point to point communication of bluetooth devices, one devices acts as a master & other as a slave. The master devices scans for available bluetooth enabled devices in vicinity. Slave devices is put in discoverable mode so that master can find it during scan. Once master device finds a device after scan, it initiates a connection. The connection is performed in 2 steps. First step is a pairing which generates a bond between two devices and confirms the identity of the devices. The pairing mechanism varies as per type of device & bluetooth version it supports. After a successful pairing between two devices, a connection is established through sockets. Let's dive into the code now.

For any bluetooth application you need to add these two permissions to your application's AndroidManifest.xml
 
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

We will use the Android bluetooth API to connect to device using sockets. The communication will be over the socket streams.

For a master device, we will create the socket connection to the other bluetooth device. Then it will continuously listen for the data from the socket stream inside a thread. It can write to connected stream outside this thread. The connection is a blocking call and bluetooth device discovery being a heavy process, may slow down the connection. So it is a good practice to cancel the device discovery before trying to connect to other device. Note that the bluetooth socket connection is a blocking call and returns only if a connection is successful or if an exception occurs while connecting to device.

The BluetoothConnection will create the socket connection to other device, once instantiated and start listening to the data from connected device.

private class BluetoothConnection extends Thread {

 private final BluetoothSocket mmSocket;

 private final InputStream mmInStream;

 private final OutputStream mmOutStream;

 byte[] buffer;

 // Unique UUID for this application, you may use different
 private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");


 public BluetoothConnection(BluetoothDevice device) {

  BluetoothSocket tmp = null;

  // Get a BluetoothSocket for a connection with the given BluetoothDevice
  try {

   tmp = device.createRfcommSocketToServiceRecord(MY_UUID);

  } catch (IOException e) {

   e.printStackTrace();

  }

  mmSocket = tmp;

  //now make the socket connection in separate thread to avoid FC

  Thread connectionThread  = new Thread(new Runnable() {

   @Override
   public void run() {

    // Always cancel discovery because it will slow down a connection
    mAdapter.cancelDiscovery();

    // Make a connection to the BluetoothSocket
    try {

     // This is a blocking call and will only return on a
     // successful connection or an exception
     mmSocket.connect();

    } catch (IOException e) {

     //connection to device failed so close the socket
     try {
      mmSocket.close();
     } catch (IOException e2) {
      e2.printStackTrace();
     }

    }
   }

  });

  connectionThread.start();

  InputStream tmpIn = null;
  OutputStream tmpOut = null;

  // Get the BluetoothSocket input and output streams
  try {

   tmpIn = socket.getInputStream();

   tmpOut = socket.getOutputStream();

   buffer = new byte[1024];

  } catch (IOException e) {

   e.printStackTrace();

  }

  mmInStream = tmpIn;
  mmOutStream = tmpOut;

 }



 public void run() {

  // Keep listening to the InputStream while connected
  while (true) {

   try {

    //read the data from socket stream
    mmInStream.read(buffer);

    // Send the obtained bytes to the UI Activity

   } catch (IOException e) {

    //an exception here marks connection loss
    //send message to UI Activity
    break;
   }
  }
 }



 public void write(byte[] buffer) {

  try {

   //write the data to socket stream
   mmOutStream.write(buffer);

  } catch (IOException e) {

   e.printStackTrace();

  }

 }

 public void cancel() {

  try {

   mmSocket.close();

  } catch (IOException e) {

   e.printStackTrace();

  }
 }

}

For a slave device, we need to put the device in discoverable mode so a master can connect to it. When the master device requests a connection, slave accepts the connection and streams to read from/write to are obtained from sockets.

For our device to be a slave Bluetooth device, put it in discoverable mode using following.

//Making the host device discoverable
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE),
      DISCOVERY_REQUEST_BLUETOOTH);
An instance BluetoothServerSocket listens for incoming requests upon discoverable mode is initiated successfully. This instance is obtained by calling the listenUsingRfcommWithServiceRecord method on the Bluetooth adapter. With this instance of BluetoothServerSocket, phone listens for incoming requests from remote devices through the start() method. Since listening is a blocking process we must use separate thread for the same.
 
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == DISCOVERY_REQUEST_BLUETOOTH) {
   boolean isDiscoverable = resultCode > 0;
   if (isDiscoverable) {
    UUID uuid = UUID.fromString(MY_UUID);
    String serverName = "BTserver";
    final BluetoothServerSocket bluetoothServer = 
      bluetoothAdapter.listenUsingRfcommWithServiceRecord(serverName, uuid);
    
    Thread listenThread = new Thread(new Runnable() {
    
     public void run() {
      try {
       
       BluetoothSocket serverSocket = bluetoothServer.accept();

      } catch (IOException e) {
      
       Log.d("BLUETOOTH", e.getMessage());
      }
     }
    });
    listenThread.start();
   }
  }
 }