First of all, I'd just like to point out that I've been learning Android for only a few weeks now so please excuse any shabby code. I'm trying to make an app which will simply list nearby bluetooth low energy devices (in the logs for now) and eventually connect to them.
Several weeks after starting this project, I have hit a brick wall with Android 6.0 location permissions that are required for a successful bluetooth scan. The following code is what I have at the moment:
package com.benstechtips.bluetoothletest4;
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.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
#TargetApi(21)
public class MainActivity extends AppCompatActivity {
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() {
Log.i("Scan Status", "Stopping Scan...");
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}, SCAN_PERIOD);
if (Build.VERSION.SDK_INT < 21) {
Log.i("Scan Status", "Starting Scan...");
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLEScanner.startScan(filters, settings, mScanCallback);
}
} else {
Log.i("Scan Status", "Stopping Scan...");
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();
}
};
Now, as far as I can tell, on a version of Android prior to version 6.0, this code would work and display the names of discovered devices (I have disabled the ability to connect to the device for the moment). However, on Android 6.0, I get the following error:
java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results
I understand that this is due to my app not having required location permissions. Although I have the following in my AndroidManifest.xml file, I know I need to request permission from the user.
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<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"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
But my question is, how do I request permission from the user?
I encountered this same problem! This was a recently added security feature in Marshmallow. Here is what requesting the permission looks like:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (this.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION},
PERMISSION_REQUEST_COARSE_LOCATION);
}
}
such that
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
I just chose coarse location. As far as I know, it doesn't matter which one you pick. You can put this in the onCreate for your main activity. Hope this helps!
Related
I tried all ways but i can't connect to the other bluetooth device. I perform these steps:
scan devices
stop scan when I find the device with "A-"
connect to device
I can find all the devices but then I can't connect to the one I want. Unfortunately I can't find a complete example
This is the code I use:
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
BluetoothManager btManager;
BluetoothAdapter btAdapter;
BluetoothLeScanner btScanner;
Button startScanningButton;
Button stopScanningButton;
TextView peripheralTextView;
BluetoothGatt bluetoothGatt;
BluetoothDevice device;
private final static int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 2;
private boolean mEchoInitialized;
private static final String TAG = "ClientActivity";
//
// private ActivityClientBinding mBinding;
private boolean mScanning;
private Handler mHandler;
private Handler mLogHandler;
private Map<String, BluetoothDevice> mScanResults;
private boolean mConnected;
private ScanCallback mScanCallback;
public static String SERVICE_STRING = "7D2EA28A-F7BD-485A-BD9D-92AD6ECFE93E";
public static UUID SERVICE_UUID = UUID.fromString(SERVICE_STRING);
public static String CHARACTERISTIC_ECHO_STRING = "7D2EBAAD-F7BD-485A-BD9D-92AD6ECFE93E";
public static UUID CHARACTERISTIC_ECHO_UUID = UUID.fromString(CHARACTERISTIC_ECHO_STRING);
public static final long SCAN_PERIOD = 5000;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
peripheralTextView = (TextView) findViewById(R.id.PeripheralTextView);
peripheralTextView.setMovementMethod(new ScrollingMovementMethod());
startScanningButton = (Button) findViewById(R.id.StartScanButton);
startScanningButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
startScanning();
}
});
stopScanningButton = (Button) findViewById(R.id.StopScanButton);
stopScanningButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
stopScanning();
}
});
stopScanningButton.setVisibility(View.INVISIBLE);
btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
btAdapter = btManager.getAdapter();
btScanner = btAdapter.getBluetoothLeScanner();
if (btAdapter != null && !btAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
// Make sure we have access coarse location enabled, if not, prompt the user to enable it
if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect peripherals.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
}
});
builder.show();
}
}
#Override
protected void onResume() {
super.onResume();
// Check low energy support
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
// Get a newer device
// logError("No LE Support.");
finish();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private ScanCallback leScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
// Log.d("ci sono",result.getDevice().getName());
if (result.getDevice().getName() != null && result.getDevice().getName().contains("A-"))
stopScanning();
// peripheralTextView.append("Device Name: " + result.getDevice().getName() + " rssi: " + result.getRssi() + "\n");
// auto scroll for text view
// final int scrollAmount = peripheralTextView.getLayout().getLineTop(peripheralTextView.getLineCount()) - peripheralTextView.getHeight();
// if there is no need to scroll, scrollAmount will be <=0
// if (scrollAmount > 0)
// peripheralTextView.scrollTo(0, scrollAmount);
connectDevice(result.getDevice());
// bluetoothGatt = result.getDevice().connectGatt(MainActivity.this, false, gattClientCallback);
}
};
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_COARSE_LOCATION: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
System.out.println("coarse location permission granted");
} else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
return;
}
}
}
public void startScanning() {
System.out.println("start scanning");
peripheralTextView.setText("");
startScanningButton.setVisibility(View.INVISIBLE);
stopScanningButton.setVisibility(View.VISIBLE);
AsyncTask.execute(new Runnable() {
#Override
public void run() {
btScanner.startScan(leScanCallback);
}
});
}
public void stopScanning() {
System.out.println("stopping scanning");
peripheralTextView.append("Stopped Scanning");
startScanningButton.setVisibility(View.VISIBLE);
stopScanningButton.setVisibility(View.INVISIBLE);
AsyncTask.execute(new Runnable() {
#Override
public void run() {
btScanner.stopScan(leScanCallback);
}
});
}
private void connectDevice(BluetoothDevice device) {
// log("Connecting to " + device.getAddress());
GattClientCallback gattClientCallback = new GattClientCallback();
bluetoothGatt = device.connectGatt(this, false, gattClientCallback);
}
protected void onStop() {
// call the superclass method first
super.onStop();
Log.d("spegni", "bluetooth");
btScanner.stopScan(leScanCallback);
}
private class GattClientCallback extends BluetoothGattCallback {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// log("onConnectionStateChange newState: " + newState);
if (status == BluetoothGatt.GATT_FAILURE) {
// logError("Connection Gatt failure status " + status);
disconnectGattServer();
return;
} else if (status != BluetoothGatt.GATT_SUCCESS) {
// handle anything not SUCCESS as failure
// logError("Connection not GATT sucess status " + status);
disconnectGattServer();
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
// log("Connected to device " + gatt.getDevice().getAddress());
setConnected(true);
gatt.discoverServices();
Log.d("discoverRiuscito", "onServicesDiscovered " + status + " " + gatt.discoverServices()
+ " " + gatt.getServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// log("Disconnected from device");
disconnectGattServer();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
// log("Device service discovery unsuccessful, status " + status);
return;
}
List<BluetoothGattCharacteristic> matchingCharacteristics = BluetoothUtils.findCharacteristics(gatt);
if (matchingCharacteristics.isEmpty()) {
// logError("Unable to find characteristics.");
return;
}
// log("Initializing: setting write type and enabling notification");
for (BluetoothGattCharacteristic characteristic : matchingCharacteristics) {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
enableCharacteristicNotification(gatt, characteristic);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
// log("Characteristic written successfully");
} else {
// logError("Characteristic write unsuccessful, status: " + status);
disconnectGattServer();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
// log("Characteristic read successfully");
readCharacteristic(characteristic);
} else {
// logError("Characteristic read unsuccessful, status: " + status);
// Trying to read from the Time Characteristic? It doesnt have the property or permissions
// set to allow this. Normally this would be an error and you would want to:
// disconnectGattServer();
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
// log("Characteristic changed, " + characteristic.getUuid().toString());
readCharacteristic(characteristic);
}
private void enableCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
boolean characteristicWriteSuccess = gatt.setCharacteristicNotification(characteristic, true);
if (characteristicWriteSuccess) {
// log("Characteristic notification set successfully for " + characteristic.getUuid().toString());
if (BluetoothUtils.isEchoCharacteristic(characteristic)) {
initializeEcho();
}
} else {
// logError("Characteristic notification set failure for " + characteristic.getUuid().toString());
}
}
private void readCharacteristic(BluetoothGattCharacteristic characteristic) {
byte[] messageBytes = characteristic.getValue();
// log("Read: " + StringUtils.byteArrayInHexFormat(messageBytes));
String message = StringUtils.stringFromBytes(messageBytes);
if (message == null) {
// logError("Unable to convert bytes to string");
return;
}
// log("Received message: " + message);
}
}
public void setConnected(boolean connected) {
mConnected = connected;
}
private void sendMessage() {
BluetoothGattCharacteristic characteristic = BluetoothUtils.findEchoCharacteristic(bluetoothGatt);
if (characteristic == null) {
return;
}
String message = "2";
byte[] messageBytes = StringUtils.bytesFromString(message);
if (messageBytes.length == 0) {
return;
}
characteristic.setValue(messageBytes);
boolean success = bluetoothGatt.writeCharacteristic(characteristic);
if (success) {
// log("Wrote: " + StringUtils.byteArrayInHexFormat(messageBytes));
} else {
// logError("Failed to write data");
}
}
public void initializeEcho() {
mEchoInitialized = true;
}
private class BtleScanCallback extends ScanCallback {
private Map<String, BluetoothDevice> mScanResults;
BtleScanCallback(Map<String, BluetoothDevice> scanResults) {
mScanResults = scanResults;
}
#Override
public void onScanResult(int callbackType, ScanResult result) {
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
for (ScanResult result : results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
mScanResults.put(deviceAddress, device);
}
}
public void disconnectGattServer() {
mConnected = false;
mEchoInitialized = false;
if (bluetoothGatt != null) {
bluetoothGatt.disconnect();
bluetoothGatt.close();
}
}
}```
I am trying to scan BLE devices continuously for a research project. I use LOW_LATENCY mode of BLE scanning. However, after 20 mins or so my UI freezes.
Basically I press button it should start scanning for BLE devices, and when I press the button again, it should stop scanning. I want to scan for at least 2 hours continuously. The following is my code.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggle = (ToggleButton) findViewById(R.id.toggleButton); // initiate a toggle button
tv = (TextView) findViewById(R.id.tv);
EasyPermissions.requestPermissions(this, "need permission", 1001, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN);
// Check BLE support for the device
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(getApplicationContext(),"no bluetooth LE functionality", Toast.LENGTH_SHORT).show();
finish();
}
btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
btAdapter = btManager.getAdapter();
bLEScanner = btAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.setScanMode(SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build();
toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#TargetApi(Build.VERSION_CODES.N)
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
Toast.makeText(getApplicationContext(), "Scanning started",
Toast.LENGTH_SHORT).show();
// The toggle is enabled
bFileName = getBluetoothFileName("Bluetooth");
bLEScanner.startScan(null,settings,bluetoothLeScanCallback);
} else {
// The toggle is disabled
Toast.makeText(getApplicationContext(), "Scanning stopped",
Toast.LENGTH_SHORT).show();
bLEScanner.stopScan(bluetoothLeScanCallback);
}
}
});
}
Scan Callback code
private ScanCallback bluetoothLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, android.bluetooth.le.ScanResult result) {
byte[] scanRecord = result.getScanRecord().getBytes();
int startByte = 2;
boolean patternFound = false;
while (startByte <= 5)
{
if ( ((int) scanRecord[startByte + 2] & 0xff) == 0x02 && //Identifies an iBeacon
((int) scanRecord[startByte + 3] & 0xff) == 0x15)
{ //Identifies correct data length
patternFound = true;
break;
}
startByte++;
}
if (patternFound)
{
//Convert to hex String
byte[] uuidBytes = new byte[16];
System.arraycopy(scanRecord, startByte + 4, uuidBytes, 0, 16);
String hexString = bytesToHex(uuidBytes);
//UUID detection
String uuid = hexString.substring(0,8) + "-" +
hexString.substring(8,12) + "-" +
hexString.substring(12,16) + "-" +
hexString.substring(16,20) + "-" +
hexString.substring(20,32);
// major
final int major = (scanRecord[startByte + 20] & 0xff) * 0x100 + (scanRecord[startByte + 21] & 0xff);
// minor
final int minor = (scanRecord[startByte + 22] & 0xff) * 0x100 + (scanRecord[startByte + 23] & 0xff);
Log.i("BLE","UUID: " +uuid + " nmajor : " +major +" nminor " +minor+" time "+getCompleteDate(System.currentTimeMillis())+" rssi "+result.getRssi());
StringBuilder bStringBuilder = new StringBuilder();
long sysTime = System.currentTimeMillis();
bStringBuilder.append(imei+","+sysTime+","+uuid+","+major+","+minor+","+result.getRssi()+","+getCompleteDate(sysTime)+","+"\n");
String finalString = bStringBuilder.toString();
exportBluetoothData(finalString, finalString.length(), "Bluetooth");
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.i("BLE", "error");
}
};
Will it be helpful to use AsyncTask for this kind of long operation? Any guide would be highly appreciated. Thanks.
It won't be helpful to use AsyncTask for this kind of long operation. Android 7.0 introduced a BLE scan timeout, where any scan running for 30 minutes or more is effectively stopped automatically: https://stackoverflow.com/a/44488991/2068732
Furthermore, BLE scan drains the battery, so you should avoid running BLE scan for so long.
If your app does require scanning frequently, you might consider stopping and re-starting the BLE scan upon a user action. But bare in mind that Android 7 prevents scan start-stops more than 5 times in 30 seconds.
you should use background thread for this process,because when you are scanning, your ui thread may be blocked that's the reason for ui freezing.make use of task scheduler or any other background process for that.
package com.myapp.services;
import java.util.List;
import android.annotation.TargetApi;
import android.app.Service;
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.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.widget.Toast;
import com.myapp.R;
/**Both RX and RSSI (Received Signal Strength Indication) are indications of the power level being received
* by an antenna
* The difference between RX and RSSI is that RX is measured in milliWatts (mW) or decibel-milliwatts (dBm)
* whereas RSSI is a signal strength percentage—the higher the RSSI number, the stronger the signal
*
*/
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BeaconService extends Service implements BluetoothAdapter.LeScanCallback{
private static final String TAG = BeaconService.class.getSimpleName();
private BluetoothGatt btGatt;
private BluetoothAdapter mBluetoothAdapter;
#Override
public void onCreate() {
super.onCreate();
writeLine("Automate service created...");
getBTService();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
writeLine("Automate service start...");
if (!isBluetoothSupported()) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
stopSelf();
}else{
if(mBluetoothAdapter!=null && mBluetoothAdapter.isEnabled()){
startBLEscan();
}else{
stopSelf();
}
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
writeLine("Automate service destroyed...");
stopBLEscan();
super.onDestroy();
if(btGatt!=null){
btGatt.disconnect();
btGatt.close();
btGatt = null;
}
}
#Override
public boolean stopService(Intent name) {
writeLine("Automate service stop...");
stopSelf();
return super.stopService(name);
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
public BluetoothAdapter getBTService(){
BluetoothManager btManager = (BluetoothManager) getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = (BluetoothAdapter) btManager.getAdapter();
return mBluetoothAdapter;
}
public boolean isBluetoothSupported() {
return this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public void startBLEscan(){
mBluetoothAdapter.startLeScan(this);
}
public void stopBLEscan(){
mBluetoothAdapter.stopLeScan(this);
}
/**
*
* #param enable
*/
public void scanLeDevice(final boolean enable) {
if (enable) {
startBLEscan();
} else {
stopBLEscan();
}
}
public static void enableDisableBluetooth(boolean enable){
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
if(enable) {
bluetoothAdapter.enable();
}else{
bluetoothAdapter.disable();
}
}
}
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
if(device!=null && device.getName()!=null){
//Log.d(TAG + " onLeScan: ", "Name: "+device.getName() + "Address: "+device.getAddress()+ "RSSI: "+rssi);
if(rssi > -90 && rssi <-1){
writeLine("Automate service BLE device in range: "+ device.getName()+ " "+rssi);
if (device.getName().equalsIgnoreCase("NCS_Beacon") || device.getName().equalsIgnoreCase("estimote")) {
//This Main looper thread is main for connect gatt, don't remove it
// Although you need to pass an appropriate context getApplicationContext(),
//Here if you use Looper.getMainLooper() it will stop getting callback and give internal exception fail to register //callback
new Handler(getApplicationContext().getMainLooper()).post(new Runnable() {
#Override
public void run() {
btGatt = device.connectGatt(getApplicationContext(), false, bleGattCallback);
Log.e(TAG, "onLeScan btGatt value returning from connectGatt "+btGatt);
}
});
}
stopBLEscan();
}else{
//Log.v("Device Scan Activity", device.getAddress()+" "+"BT device is still too far - not connecting");
}
}
}
BluetoothGattCallback bleGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
writeLine("Automate service connection state: "+ newState);
if (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED){
writeLine("Automate service connection state: STATE_CONNECTED");
Log.v("BLEService", "BLE Connected now discover services");
Log.v("BLEService", "BLE Connected");
new Thread(new Runnable() {
#Override
public void run() {
writeLine("Automate service go for discover services");
gatt.discoverServices();
}
}).start();
}else if (newState == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED){
writeLine("Automate service connection state: STATE_DISCONNECTED");
Log.v("BLEService", "BLE Disconnected");
}
}
#Override
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
writeLine("Automate service discover service: GATT_SUCCESS");
Log.v("BLEService", "BLE Services onServicesDiscovered");
//Get service
List<BluetoothGattService> services = gatt.getServices();
writeLine("Automate service discover service imei: " +imei);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
};
private void writeLine(final String message) {
Handler h = new Handler(getApplicationContext().getMainLooper());
// Although you need to pass an appropriate context
h.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(),message,Toast.LENGTH_SHORT).show();
}
});
}
}
In manifest.xml
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<!-- Permission for bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<service
android:name="com.myapp.services.BeaconService"
android:enabled="true"
android:exported="false" />
I used SCAN_MODE_BALANCED instead of SCAN_MODE_LOW_LATENCY Now it doesn't freeze the thread. Thank you for all your answers.
The only services i got are
00001800-0000-1000-8000-00805f9b34fb -- Generic Access
00001801-0000-1000-8000-00805f9b34fb -generic attribute
8e400001-f315-4f60-9fb8-838830daea50 -- Buttonless DFU service
0000fee7-0000-1000-8000-00805f9b34fb --
i dont know how to find heart service, calories etc.
the band i am using is Rc-wh10 device name - M2S
MainActivity.java
package iband.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.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
#TargetApi(21)
public class MainActivity extends AppCompatActivity {
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;
TextView tw;
RecyclerView recyclerView;
ArrayList<BluetoothProvider> list;
iband.bleexample.BluetoothAdapter adapter;
TextView datatodisplay;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//datatodisplay = (TextView) findViewById(R.id.textView);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
list = new ArrayList<>();
adapter = new iband.bleexample.BluetoothAdapter(this, list);
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayout.VERTICAL, false));
recyclerView.setAdapter(adapter);
recyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(MainActivity.this, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
// TODO Handle item click
//Toast.makeText(MainActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
BluetoothDevice bd = new iband.bleexample.BluetoothAdapter(MainActivity.this, list).getDeviceAddress(position);
connectToDevice(bd);
}
})
);
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();
tw = (TextView) findViewById(R.id.textView);
}
#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>();
}
Toast.makeText(this, "Scanning...", Toast.LENGTH_SHORT).show();
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.
Toast.makeText(MainActivity.this, "Please enable Bluetooth", Toast.LENGTH_SHORT).show();
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());
tw.setText(result.toString());
// Log.i("result", result.isConnectable()+"");
//Log.i("result", result.getDevice().getName().toString()+"");
// Toast.makeText(MainActivity.this, result.toString()+"", Toast.LENGTH_SHORT).show();
BluetoothDevice btDevice = result.getDevice();
list.add(new BluetoothProvider(btDevice.getName(), btDevice.getAddress(), btDevice));
adapter.notifyDataSetChanged();
//connectToDevice(btDevice);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
for (ScanResult sr : results) {
Log.i("ScanResult - Results", sr.toString());
tw.setText("ScanResult - Results" + sr.toString());
}
}
#Override
public void onScanFailed(int errorCode) {
Log.e("Scan Failed", "Error Code: " + errorCode);
tw.setText("Scan Failed");
}
};
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());
tw.setText(device.getName());
connectToDevice(device);
}
});
}
};
public void connectToDevice(BluetoothDevice device) {
if (mGatt == null) {
mGatt = device.connectGatt(this, false, gattCallback);
Log.i("ScanResult - Results", mGatt.toString());
//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.size() + services.toString());
for (int i = 0; i < services.size(); i++) {
Log.i("Services UUID", services.get(i).getUuid() +
"");
for (int j = 0; j < services.get(i).getCharacteristics().size(); j++) {
//gatt.readCharacteristic(services.get(i).getCharacteristics().get(j));
Log.i("Characterstics UUId", services.get(i).getCharacteristics().get(j).getUuid() + "");
}
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic
characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
Toast.makeText(MainActivity.this, characteristic.toString() + "", Toast.LENGTH_SHORT).show();
//gatt.disconnect();
}
};
}
I am trying to scan other Bluetooth devices in Android using below code.
But nothing is getting triggered and no devices are detected.
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 = 1000000;
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);
mLEScanner.startScan( 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();
}
};
}
I have these in the Manifest.
Added the coarse location as for BLE accuracy it is needed.
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true" />
Few things you have to check.
Is your android device is supporting BLE?
for this use (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
If android version is 6.0 then allow location Access form -Settings-> app
make sure your Location is enable in android device.
I am working on a hardware device which sends a continuous stream of data over BLE to an Android App. The android app receives this data as GATT notifications, then processes this data and saves it to a database.
The configuration details of the project are as follows:
Phone - Moto E 1st Generation
Android Version - Android 5.1 - Lollipop
iOS - iPhone 4 & 5, tested on iOS 7 & 8
Hardware - CC2541
Connection_Interval : 40 ms (Set in the firmware of the hardware).
Packets Sent Per Connection Interval : 4 (Set in the firmware of the hardware).
THE PROBLEM
When data is transmitted from the hardware device to the BLE data capture app running on Android phone, all the data packets are not received. It receives only about 35-45 packets, whereas the expected number of packets is 50.
What is more surprising is that when we used the BLE packet sniffer, there was a perfect match between the data sniffed and displayed by the Android phone (which is incomplete / incorrect data). This leads me to believe that the hardware is behaving differently when connecting to the Android phone and not sending all of the data.
When we use the same hardware with an iOS BLE data capture app, the data is received correctly.
I am puzzled and clueless about this behaviour of BLE data capture in Android. How the app on iOS device is able to capture all the data correctly whereas app on android phone is not able to capture data properly at all?
Has anyone experienced such a problem of packet loss / incorrect data when using BLE app on Android? Any inputs are welcome. Thank you so much for your help in advance.
The android App is using the standard BLE code to connect to the device over BLE. The Android code that I am using is shown below:
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.BluetoothGattDescriptor;
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.nfc.Tag;
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 BLETestActivity extends Activity {
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_bletest);
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) {
Log.i("innnnn","spinnnn - " + Build.VERSION.SDK_INT);
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 = null;/* 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());
for(int i=0;i<services.size();i++) {
List<BluetoothGattCharacteristic> charList = services.get(i).getCharacteristics();
for (int j = 0; j < charList.size();j++) {
BluetoothGattCharacteristic characteristic = charList.get(j);
if (characteristic.getUuid().compareTo(Constants.HEART_DATA_UUID) == 0) {
Log.i(BLETestActivity.class.getSimpleName(), "Characteristic UUID is " + characteristic.getUuid());
mGatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Constants.HEART_DATA_DESCRIPTOR_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mGatt.writeDescriptor(descriptor);
}
}
}
}
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
// Retrieve the byte values
// Convert them to hex values
byte[] data = characteristic.getValue();
// The following array contains data in the follwing form:
// 0-1 - running counter counts
// 2-4, 5-7, 8-10 - Sample 1 data for channel 1, channel2 and channel 3
// 11-13, 14-16, 17-19 - Sample 2 data for channel 1, channel 2 and channel 3
// Log.i(TAG," ------ " + Thread.currentThread().getId());
String heartBeatDataInHex = bytesToHex(data);
// An error packet is received after every 17 samples.
// Checking to make sure that this is not an error packet
if (!(heartBeatDataInHex.substring(4, 10).equalsIgnoreCase("FFFFFF") && heartBeatDataInHex.substring(26, 40).equalsIgnoreCase("FFFFFFFFFFFFFF"))) {
Log.i("testdata", heartBeatDataInHex + " ---- " + Thread.currentThread().getId());
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic
characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
gatt.disconnect();
}
};
private String bytesToHex(byte[] bytes) {
final char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
I've had similar problem!
I have made some testing and I've determined that the minimum connection interval is 48ms with android api level 18.
(connection interval for BLE on Galaxy S3 Android 4.3).
Posible solution:
Make the connection interval slower (less than 48ms).
From api level 21 to upper, you can change CONNECTION_PRIORITY android developer to modify the interval connection but this implies more consumption of energy.