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.
Born in New Delhi, India. A software engineer by profession, an android enthusiast and an evangelist. My motive here is to create a group of skilled developers, who can develop something new and good. Reason being programming is my passion, and also it feels good to make a device do something you want. In a very short span of time professionally I have worked with many tech firms. As of now too, I am employed as a senior engineer in a leading tech company. In total I may have worked on more than 20 projects professionally, and whenever I get spare time I share my thoughts here at Truiton.
Thanks
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+
i m getting after debugging ,on android device ‘bluetooth has stopped working’ and i find nothing.
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?
I have the same problem as yours. Did you fix it ?
Please comment here if you found a solution to this problem. Thank you.
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.
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
Can you please tell us how do we create a scanFilter Array?
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.
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.
Can you give an simple project about this?
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
Hi can u please help me how to connect multiple ble devices with a single android application and read values..?
Thanks!
This really helped me through comprehending bluetooth 🙂
where I can get the package of the complete source code of this tutorial?
thanks!
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) ?
can any body tell, in case of API level > 21, how to ping the device repeatedly for data??
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….
This tutorial definitely made my day… So clean and precise! Thank you very very VERY much! 🙂
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
I am using the above code to connect to a ble device but my on scan result is never called can anyone help.
If you use Android 6.0+, you need turn on GPS.
Details:http://stackoverflow.com/questions/33013818/bluetoothlescanner-startscan-with-android-6-0-does-not-discover-devices
BluetoothGattCallback CB is not getting called after the connection with BLE device.. Could you please give me some idea..?
“21” can be replaced by “Build.VERSION_CODES.LOLLIPOP”
In mHandler = new Handler();
i have some problem and also in mHandler.postDelayed(new Runnable() {
so how solved?
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 .
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
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.
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.