Android Bluetooth Low Energy (BLE) Example - Truiton
Skip to content

Android Bluetooth Low Energy (BLE) Example

Android Bluetooth Low Energy

As you might be aware, Bluetooth technology has been one of the most used technology when a connection has to be established with a remote device. But this technology also has a major limitation, i.e. high battery consumption. Therefore an improved version of this technology with low energy consumption was introduced, called BLE (Bluetooth Low Energy). Therefore also on Android Bluetooth Low Energy was introduced with API 18 (Android 4.3). This opened up a whole new dimension in the world of Android development. All sorts of new hardware devices were introduced in the market, with compliance to Bluetooth low energy standard, like heart rate monitors, fitness devices and many more. Since this Android Bluetooth low energy API is a little new. Till now there is no proper code example available for its implementation. Therefore here in this tutorial, I will show you how to make an Android Bluetooth Low Energy Example, with latest APIs.

Since BLE is still new to Android, some improvements were made in API 21 for the same, where the way to detect low energy Bluetooth devices was changed. This lead to deprecation of some old APIs. Primarily scanning for available Bluetooth LE devices using, mBluetoothAdapter.startLeScan(mLeScanCallback) method is now deprecated. The new API suggests to use startScan method of BluetoothLeScanner class, which can be initialized from the same BluetoothAdapter class. But the problem which many of you must have faced, is these new API methods do not work on old APIs, as they are not backward compatible. Hence here we will make an Android Bluetooth Low Energy example, with backward support till API 18.

Android Bluetooth Low Energy Example

Since BLE was introduced in API 18 and cannot be used on old devices, due to change of Bluetooth specifications. I suggest you to specify the min SDK version to 18 in your app. Next add these permissions and feature tags in the manifest tag of your app manifest:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Since I will be printing all the data in logs, there is no need for a layout file, lets have a look at the code for using Bluetooth low energy on Android:

package com.truiton.bleexample;

import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

@TargetApi(21)
public class MainActivity extends ActionBarActivity {
    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private Handler mHandler;
    private static final long SCAN_PERIOD = 10000;
    private BluetoothLeScanner mLEScanner;
    private ScanSettings settings;
    private List<ScanFilter> filters;
    private BluetoothGatt mGatt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "BLE Not Supported",
                    Toast.LENGTH_SHORT).show();
            finish();
        }
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();
                filters = new ArrayList<ScanFilter>();
            }
            scanLeDevice(true);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            scanLeDevice(false);
        }
    }

    @Override
    protected void onDestroy() {
        if (mGatt == null) {
            return;
        }
        mGatt.close();
        mGatt = null;
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT < 21) {
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                    } else {
                        mLEScanner.stopScan(mScanCallback);

                    }
                }
            }, SCAN_PERIOD);
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            } else {
                mLEScanner.startScan(filters, settings, mScanCallback);
            }
        } else {
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            } else {
                mLEScanner.stopScan(mScanCallback);
            }
        }
    }


    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.i("callbackType", String.valueOf(callbackType));
            Log.i("result", result.toString());
            BluetoothDevice btDevice = result.getDevice();
            connectToDevice(btDevice);
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult sr : results) {
                Log.i("ScanResult - Results", sr.toString());
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.e("Scan Failed", "Error Code: " + errorCode);
        }
    };

    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("onLeScan", device.toString());
                            connectToDevice(device);
                        }
                    });
                }
            };

    public void connectToDevice(BluetoothDevice device) {
        if (mGatt == null) {
            mGatt = device.connectGatt(this, false, gattCallback);
            scanLeDevice(false);// will stop after first device detection
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.i("onConnectionStateChange", "Status: " + status);
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.i("gattCallback", "STATE_CONNECTED");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.e("gattCallback", "STATE_DISCONNECTED");
                    break;
                default:
                    Log.e("gattCallback", "STATE_OTHER");
            }

        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            List<BluetoothGattService> services = gatt.getServices();
            Log.i("onServicesDiscovered", services.toString());
            gatt.readCharacteristic(services.get(1).getCharacteristics().get
                    (0));
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic
                                                 characteristic, int status) {
            Log.i("onCharacteristicRead", characteristic.toString());
            gatt.disconnect();
        }
    };
}

Bluetooth LE is huge topic, hence the example above only shows how to scan available Bluetooth low energy devices, discover their services and read the basic characteristics of it. The above example shows the best practices to detect a BLE device in an Android app with support up to API 18. To keep it short and simple, instead of writing full code for selecting a Gatt profile/service. I have randomly selected a Generic attribute (Gatt) profile above to print its BluetoothGattCharacteristic object using gatt.readCharacteristic(services.get(1).getCharacteristics().get(0)). To actually read BLE attributes, you may need to write code specifically for your application as there may not be a single way to do it, as there are around 50 Gatt based profile specifications. Hence lets understand each component of Android Bluetooth Low Energy API individually.

Working of Bluetooth LE in Android

As I mentioned BLE has different Gatt profiles, each BLE device has a profile, through which they can act as a server. Like all network devices, Bluetooth LE also works in a client/server manner. Here in the example above, mobile app would act as a client and the BLE device would act as a server.

Scanning Bluetooth Low Energy Devices

In the above example I scanned BLE devices using the highest power consumption mode ScanSettings.SCAN_MODE_LOW_LATENCY. As here my objective was to detect nearby BLE devices immediately. But in total, the new API offers three modes in which nearby BLE devices could be scanned.

  • ScanSettings.SCAN_MODE_LOW_POWER – This is the default BLE scan mode, consuming the lowest power, but has a high latency. Good for use when BLE device detection needs to be done in background.
  • ScanSettings.SCAN_MODE_BALANCED – This mode scans Bluetooth LE devices in a balanced power consumption mode, with respect to latency.
  • ScanSettings.SCAN_MODE_LOW_LATENCY – This mode uses the highest power, when compared to other modes. Also detects the BLE devices fastest, hence should be used when the app is in foreground.

Reading BLE Characteristics in Android

BLE characteristic is the actual data which is served through the BLE device. For example heart rate from a heart rate BLE device is a characteristic. Reading BLE characteristics or getting notified of changed BLE characteristics could be a whole different tutorial. As this could have a huge amount of customizations. Therefore here lets lets discuss the basic part only to get you started.

To read Bluetooth low energy characteristics after scanning, we first need to connect to a BLE device using connectToDevice method as shown above. Then further a connection needs to be established with a Gatt profile using device.connectGatt method, returning connection callbacks to the BluetoothGattCallback class also as shown in the example above. Further when a connection is established we need to discover all the services using gatt.discoverServices() method. This would invoke onServicesDiscovered method, where you can select a service and determine the types of characteristics it has. If the characteristic type is read, then the gatt.readCharacteristic method could be used, which would fire the onCharacteristicRead callback as shown above. But if characteristic type is notify, you may need to set notifications using the setCharacteristicNotification method. More of this could be read from here, as reading Bluetooth Low Energy characteristic notifications is not in our scope. Hope this Android Bluetooth Low Energy example helped you in getting started and scanning your first BLE device. Connect with us on Facebook, Google+ and Twitter for more updates.

30 thoughts on “Android Bluetooth Low Energy (BLE) Example”

  1. Have you noted that as a BTLE client (central) the Android scans (>= 5.0) use random addressing. This causes some devices to immediately reject the attempt to connect. Do you know of a way to turn that off?

    Also, I am confused about what the API actually does when connectGatt (connectGatt(this, false, gattCallback); is called with the option set to true rather than false. My understanding is that it will ‘autoscan’ for devices previously connected. There appears to be an element of truth to that but it is very unreliable. So it is not clear to me how that feature is related to gattClose().

    gattClose() is also a mystery. When should I call it and when should I not call it? It appears if I call gattClose() after a disconnect the autoconnect will NEVER work. I am guessing I might have to re-do connections from the start and re-discover everything. I am having a lot of troubles getting consistent behavior of connections when more than one device is paired and I try and connect and or reconnect to these devices. In many cases the call to connect simply does not cause any response at all. Logcat sits there and shows nothing. If you have any better understanding of what connectGatt() does and what gattClose() does, I would greatly appreciate it!!!

    Thanks,

    Brian

    PS: Great explanations on what you have displayed so far. I will need to make my apps compatible with 5.0+

    1. I’m getting a “BluetoothLeScanner: could not find callback wrapper” message in debug… I’ve added logs, and debugged ’til I’m blind. The code is exactly as yours, with the addition of some log.i calls for my own peace of mind…

      Any suggestions for why the callback wrapper is getting lost?

          1. I think I found it. ScanLeDevice is called twice: once with the enable argument set to true (telling it to start scanning), and then again with enable set to false (telling it to stop scanning). The problem is in the if-else clause. The first time it is called (enable = true), mLEScanner.startScan is called, but mLEScanner.stopScan is also called, but postDelayed. When ScanLeDevice is called the second time (enable = false), it calls mLEScanner.stopScan for the second time. So even if your device connects before postDelayed timer is done, stopScan will get called when the timer runs out. If you’re device connects, it will always do this.

            My OOP is pretty weak, but I think that the scan is stopping fine. When the stopScan is called for a second time, that’s the wrapper that is being lost. My solution was to comment out the second mLEScanner.stopScan(mScanCallback). That would be line 131 on this page, which is inside the else part of the outer if-else clause. You can also probably remove the call to scanLeDevice that is inside the connectToDevice method. That essentially does the same thing.

            Using a timer is a good practice for BLE scanning, so I will be looking for a way to include a timer and a stop command that can begin when the device has been found/connected to. This code has been very helpful though.

          2. I Found the problem.
            Since Android 6 you need to give the app permission for ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION otherwise it wont work.
            I gave it the permissions and now it’s working fine.
            Thanks for sharing your code

  2. Hiiiii..
    when i used this code for scan ble device and connect it. It connects for sometime and after that automatically disconnect.. In nexus 4 at version 5.1.. plz help me.

  3. Hai. I want to send a string or a command to my blue sensor device from the mobile app. Currently my app is able to scan the devices, connect to the devices and read the data that is sent from the sensor. Now i want to send a command to the sensor from the app. Please guide me how to do that.

  4. Hi Mohit Gupt,

    could you please add a GITHUB witht this tutorial it’s really nice. But sometimes is better to have the running source to understand also all the process.

    Thank you again for your valuable information.

    Best regards,
    Teocci

  5. Cool example. I dig more into API documentation on Android Developers website and came to know that for API level above 21, you can set Scan settings with different interval and scan window. And “Low_Power” if you don’t pass anything.

    Can you please tell me what is the interval and window for scanning in API level below 21. (Android 4.4 and 4.3) ?

  6. What’s the point of this:

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
    BluetoothGattCharacteristic
    characteristic, int status) {
    Log.i(“onCharacteristicRead”, characteristic.toString());
    ===========> gatt.disconnect();
    }

    That line had me hours wondering why the link established only for half a second….

  7. You show how to read a message from a device to Android
    Can you show me how to write a message from android to a devece
    Thanks

  8. I want to create background service for Bluetooth low energy on Android. I want to scan and connect ble device through my app when app is in background. After this i want to notify and read all the characteristic of connected ble device .

  9. Hi Mohit,
    It’s a nice and informative blog.
    Thanks for sharing this.
    I am new to these BLE connectivity task.
    I have an issue that how can we auto connect Android bluetooth with BLE?
    Can you please help me in the same?

    Thanks

  10. Hi Mohit
    I am using same code, it is working all devices except two devices.
    which is nexus 6P (Marshmallow), Moto M (nougat).
    could you please help me out what is the problem in this devices.

  11. Thank you Mohit Gupt, it’s amazing, It helps me so much

    I trying make a gattconnection with sierra wireless BC118 BLE, but I can’t, maybe melodysmart have a proprietary libraries or something like that. I make a discover of gatt services but I don’t know which services i cand send a message(20 bytes) to a IOT devices that it have a module BC118. there are two UUID custom in BC118 that i see with a Ble scanner comercial app

    thank you

    David Steckler.

Leave a Reply

Your email address will not be published. Required fields are marked *