I'm developing an app that have to connect with a BLE device, in my code I want to use the new Scan and ScanCallback for BLE implemented from API 21 (Android 5) but I have to maintain the compatibility with Android 4.3 and above.
So I wrote the code, for example, in this way:
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner.startScan(filters, settings, mScanCallback);
} else {
btAdapter.startLeScan(leScanCallback);
}
And I have defined the 2 callbacks, one for API 21 and above and one for API 18 to 20:
//API 21
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice btDevice = result.getDevice();
connectToDevice(btDevice);
}
public void connectToDevice(BluetoothDevice device) {
if (mGatt == null) {
mGatt = device.connectGatt(context, false, btleGattCallback);
if (Build.VERSION.SDK_INT < 21) {
btAdapter.stopLeScan(leScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}
};
//API 18 to 20
private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
btAdapter.stopLeScan(leScanCallback);
runOnUiThread(new Runnable() {
#Override
public void run() {
mBluetoothGatt = device.connectGatt(context, false, btleGattCallback);
}
});
}
};
I also added the annotation
#TargetApi(21)
but when I launch the App on Android 4.x it crashes immediately reporting the error that the class ScanCallback cannot be found (the one intended to be used only with Android 5 and above).
How can I solve this?
Thank you very much.
Daniele.
After reading several posts I did the following. Just in case, here is the documentation of Android about BluetoothLe
First
Create two methods one scanLeDevice21 and scanLeDevice18. On scanLeDevice21 add the annotation #RequiresApi(21) that says:
Denotes that the annotated element should only be called on the given API level or higher.
This is similar in purpose to the older #TargetApi annotation, but more clearly expresses that this is a requirement on the caller, rather than being used to "suppress" warnings within the method that exceed the minSdkVersion.
Second
Implement each method, here's my code.
#RequiresApi(21)
private void scanLeDevice21(final boolean enable) {
ScanCallback mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice bluetoothDevice = result.getDevice();
if (!bluetoothDeviceList.contains(bluetoothDevice)) {
Log.d("DEVICE", bluetoothDevice.getName() + "[" + bluetoothDevice.getAddress() + "]");
bluetoothDeviceArrayAdapter.add(bluetoothDevice);
bluetoothDeviceArrayAdapter.notifyDataSetChanged();
}
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
final BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(() -> {
mScanning = false;
swipeRefreshLayout.setRefreshing(false);
bluetoothLeScanner.stopScan(mLeScanCallback);
}, SCAN_PERIOD);
mScanning = true;
bluetoothLeScanner.startScan(mLeScanCallback);
} else {
mScanning = false;
bluetoothLeScanner.stopScan(mLeScanCallback);
}
}
/**
* Scan BLE devices on Android API 18 to 20
*
* #param enable Enable scan
*/
private void scanLeDevice18(boolean enable) {
BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi,
byte[] scanRecord) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
bluetoothDeviceArrayAdapter.add(bluetoothDevice);
bluetoothDeviceArrayAdapter.notifyDataSetChanged();
}
});
}
};
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(() -> {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
Third
Every time you need to scan devices you surround your code asking which version you are. For instance, I have a RefreshLayout to display the list of devices. Here's the result:
/**
* Refresh listener
*/
private void refreshScan() {
if (!hasFineLocationPermissions()) {
swipeRefreshLayout.setRefreshing(false);
//Up to marshmallow you need location permissions to scan bluetooth devices, this method is not here since is up to you to implement it and it is out of scope of this question.
requestFineLocationPermission();
} else {
swipeRefreshLayout.setRefreshing(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scanLeDevice21(true);
} else {
scanLeDevice18(true);
}
}
}
And that's it.
Forget about extending, subclassing classes you don't really need like ulusoyca answer.
Create AbstractBluetoothLe class and IBleScanCallback interface. IBleScanCallback interface is a Marker Interface. In other saying, an interface with no methods. You can also add methods to interface if you need. These methods will do the same functionality for all type of scanCallbacks i.e. getListOfFoundBleDevices(), clearListOfFoundBleDevices() etc.
Create BluetootLeLollipop, and BluetoothLeJellyBean classes which extend AbstractBluetoothLe class. Create also BluetootLeMarshmallow class which extends BluetootLeLollipopclass. AbstractBluetoothLe class has protected field mIBleScanCallback which is an IBleScanCallback object.
Create BleScanCallbackBase class which implements IBleScanCallback.
Create LollipopScanCallback class which extends ScanCallback class and implements IBleScanCallback interface. .This class has a protected field scanCallback which will be instantiated as BleScanCallbackBase object. Create also MarshmallowScanCallback class which extends LollipopScanCallback class.
Create JellyBeanScanCallback class which extends BleScanCallbackBase and implements BluetoothAdapter.LeScanCallback
In BleScanCallbackBase override the method: onScanCallback(...)
In LollipoScanCallback override onScanResult(int callbackType, ScanResult result) and inside this method call the method onScanCallback(...) of the scanCallback object.
In JellyBeanScanCallback override onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) and inside this method call onScanCallback(...)
Finally, do whatever needs to be done when a device is found in onScanCallback(...)method of BleScanCallbackBase class.
In short, read about composition over inheritance- I know this is not an answer to your question but this is a neat way of what you want to achieve in the end. Here is the class diagram:
Your code is crashing because it is creating anonymous inner class. Hence at run time it doesn't find that sCanCallback class.
Try below way and share the outcome. Before you try this, make sure you comment the callback(ScanCallback).
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner.startScan(filters, settings, new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice btDevice = result.getDevice();
connectToDevice(btDevice);
}
public void connectToDevice(BluetoothDevice device) {
if (mGatt == null) {
mGatt = device.connectGatt(context, false, btleGattCallback);
if (Build.VERSION.SDK_INT < 21) {
btAdapter.stopLeScan(leScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}
};
} else {
btAdapter.startLeScan(leScanCallback);
}
I would like to share my idea to overcome this crash on devices below API 21. The ScanCallback class has support from API 21. So when you write a code to implement ScanCallback in your scan code it will cause a crash on lover APIs. I have fixed it the following way:
I created one new abstract class which extending ScanCallback class as follows:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class AppScanCallback extends ScanCallback {}
Now I am using this class instance in my BluetoothService class as follow:
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private AppScanCallback mScanCallback;
And using this variable as follow:
public void startBleScan() {
if (isEnabled()) {
if (Build.VERSION.SDK_INT < 21) {
_bluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLEScanner = _bluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.build();
filters = new ArrayList<>();
mScanCallback = new AppScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
if (Build.VERSION.SDK_INT >= 21) {
BluetoothDevice btDevice = result.getDevice();
onLeScanResult(btDevice, result.getScanRecord().getBytes());
}
}
#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);
}
};
mLEScanner.startScan(filters, settings, mScanCallback);
}
}
}
and to stop scan
public void stopBLEScan() {;
if (Build.VERSION.SDK_INT < 21) {
_bluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
Hope it will help you out.
Related
I have faced with the issue using startScan method of BluetoothLeScanner a BLE device was found, but when I turned off BLE device my phone still shows this device as turned on !!
I have tried to use:
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
Log.i("ScanCallback", String.format("onScanResult(int callbackType[%d], ScanResult result)", callbackType));
final BluetoothDevice btDevice = result.getDevice();
if (btDevice == null){
Log.e("ScanCallback", "Could not get bluetooth device");
return;
}
final String macAddress = btDevice.getAddress();
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
// NOTE: I've never got here
final BluetoothDevice outOfRangeDevice = mBtDevices.get(macAddress);
...
} else {
...
}
}
...
};
Guy, I have not found solution how to detect that BLE device is lost in other resources like (Android SDK reference, forums, stackoverflow and etc) (:
Any help will be appreciated !!
During googling and exploring the Android Documentations I have figured out how to detect if device is out of range. I would like to share my solution how I did it:
...
public void scanBLEDevices(final boolean enable) {
if(mLeScanner == null) {
Log.d(TAG, "Could not get LEScanner object");
throw new InternalError("Could not get LEScanner object");
}
if (enable) {
startLeScan();
} else {
stopLeScan(false);
}
}
private void startLeScan() {
Log.i(TAG, "startLeScan(BluetoothLeScanner mLeScanner)");
mScanning = true;
mInRangeBtDevices.clear();
if (mStartScanCallback != null) {
mHandler.removeCallbacks(mStartScanCallback);
}
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLeScanner.startScan(mScanFilters, mScanSettings, mScanCallback);
}
mStopScanCallback = new Runnable() {
#Override
public void run() {
stopLeScan(true);
}
};
mHandler.postDelayed(mStopScanCallback, SCAN_PERIOD);
}
private void stopLeScan(final boolean isContinueAfterPause) {
Log.i(TAG, "stopLeScan(BluetoothLeScanner mLeScanner)");
mScanning = false;
if (mStopScanCallback != null) {
mHandler.removeCallbacks(mStopScanCallback);
}
removeOutOfRangeDevices();
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLeScanner.stopScan(mScanCallback);
}
if (isContinueAfterPause) {
mStartScanCallback = new Runnable() {
#Override
public void run() {
startLeScan();
}
};
mHandler.postDelayed(mStartScanCallback, SCAN_PAUSE);
}
}
private void removeOutOfRangeDevices() {
final Set<String> outOfRangeDevices = new HashSet<>();
for (String btAddress : mBtDevices.keySet()) {
if (!mInRangeBtDevices.contains(btAddress)) {
outOfRangeDevices.add(btAddress);
}
}
for (String btAddress : outOfRangeDevices) {
final BluetoothDevice outOfRangeDevice = mBtDevices.get(btAddress);
mBtDevicesRSSI.remove(btAddress);
mBtDevices.remove(btAddress);
}
}
...
Explanation:
As you can see I have added on each scanning period mInRangeBtDevices collection that will keep all devices found during the current scanning.
When I stop scanning, I am also removing out of range device from previous lists that is not available anymore using one additional helper collection outOfRangeDevices
I think this example would be usefull and you will be able to integrate it in your own code
This one is looking good (JAVA):
As I understood, you need to implement startLeScan().
Find BLE devices
To find BLE devices, you use the startLeScan() method. This method takes a BluetoothAdapter.LeScanCallback as a parameter. You must implement this callback, because that is how scan results are returned. Because scanning is battery-intensive, you should observe the following guidelines:
As soon as you find the desired device, stop scanning.
Never scan on a loop, and set a time limit on your scan. A device that was previously available may have moved out of range, and continuing to scan drains the battery.
The following snippet shows how to start and stop a scan:
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
...}
Consider checking this tutorial as well.
Also this one.
I have a problem with my code:
private ScanCallback mLeScanCallback = new ScanCallback(){
//Callback when a BLE advertisement has been found.
#Override
public void onScanResult(int callbackType, final android.bluetooth.le.ScanResult result) {
super.onScanResult(callbackType, result);
new Thread(){
#Override
public void run() {
final BluetoothDevice device = result.getDevice();
runOnUiThread(new Runnable() {
#Override
public void run() {
if (device != null){
mDevices.add(device);
}
}
});
}
}.start();
}
//Callback when batch results are delivered.
#Override
public void onBatchScanResults(List<android.bluetooth.le.ScanResult> results) {
super.onBatchScanResults(results);
}
//Callback when scan could not be started.
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
currently I am using this code to get the results of my scan. This was based on: https://github.com/RedBearLab/Android/blob/master/Examples/Chat/src/com/redbear/chat/Main.java#L138
The app where is was based on had a lower API level, and my app has a higher one. So I changed it to on startScan() method.
I am honestly stuck, because when I run the app i get no errors. I checked if the mDevices array is empty and it is. Meaning that the code doesn't add the devices to the array or that there aren't any devices to be found by my app specifically.
Any help would be greatly appreciated.
Code of activating scanning
private void scanDevice(){
new Thread() {
#Override
public void run(){
BTScanner.startScan(mLeScanCallback);
try {
Thread.sleep(SCAN_PERIOD);
} catch (InterruptedException e){
e.printStackTrace();
}
BTScanner.stopScan(mLeScanCallback);
}
}.start();
}
Found the problem. It has to do with the permissions for ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION to get the scan results.
I've seen plenty of questions about how to connect to multiple devices purposely. But in my situation, I am only trying to connect to one hardware device.
I have two hardware devices that are supposed to do the same thing. When they connect to my app via BLE, then they have an LED that turns a solid color. This all works fine and dandy when I only have one device turned on. However, when I turn two of the devices on and then try to connect to just one. Both of the devices' LED's turn solid. Although I don't seem to be getting any incoming data from the one that I didn't intend to connect to.
I don't think it's the device's fault. Because I don't have this issue on iOS. I think the phone might be remembering previously connected devices somewhere maybe?
I'm sorry, this is a lot of code. But I feel like it's important to have this whole class. Any help is much appreciated.
package com.roberts.croberts.orange;
import android.annotation.TargetApi;
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.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.le.BluetoothLeScanner;
import java.util.ArrayList;
import java.util.List;
#TargetApi(21)
public class BluetoothRegulator {
private static BluetoothRegulator instance = null;
private Context context;
private BluetoothLeScanner mLEScanner;
private BluetoothDevice orangeDevice;
//scanner stuff
private Handler mHandler;
// Stops scanning after 3 seconds.
private static final long SCAN_PERIOD = 3000;
//connected stuff
private android.bluetooth.BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt;
public ArrayList<BluetoothDevice> devices = new ArrayList<>();
private Handler foundHandler = new Handler();
private Handler servicesHandler = new Handler();
private ScanCallback mScanCallback;
public static BluetoothRegulator sharedInstance(){
if(instance == null) {
instance = new BluetoothRegulator();
Log.i("chase", "created new instance");
}
return instance;
}
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mBluetoothGatt.discoverServices();
Log.i(TAG, "BR: onconnectionsStateChanged Connected to GATT server.");
// Attempts to discover services after successful connection.
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Loops through available GATT Serviceokay so ees.
for (BluetoothGattService gattService : mBluetoothGatt.getServices()) {
for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
Log.i(TAG, mBluetoothGatt == null ? "mbluetooth is null" : "should be subscribed");
}
}
Log.i("chase", "did connect and discover devices");
} else {
Log.w(TAG, "Not Success onServicesDiscovered received: " + status);
connect(orangeDevice);
}
}
private Object getFieldFromObject(Object obj, String name){
try {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
}catch(Exception e){
Log.i("chase", "e: "+e);
return null;
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.i("BR: chase", "received data!");
}
};
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
public boolean initialize(android.bluetooth.BluetoothManager btmanager, Context ctx) {
mBluetoothManager = btmanager;
context = ctx;
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
setUpCallBack();
}
return true;
}
public void scan() { //we call scan when they hit the connect button...
// Stops scanning after a pre-defined scan period.
Log.i("chase", "start scanning");
devices = new ArrayList<>();
if (mHandler == null) mHandler = new Handler();
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(mScanCallback);
}
}
private void foundDevice(BluetoothDevice device){
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 5 && (deviceName.substring(0, 6).equals("orange") || deviceName.substring(0, 6).equals("smartb"))) {
for (BluetoothDevice d : devices){
if (d.getAddress().equals(device.getAddress())){
return;
}
}
mHandler.removeCallbacksAndMessages(null);
devices.add(device);
if (devices.size() == 1) { //wait one second and then assume there aren't any more devices named "orange"
foundHandler.postDelayed(new Runnable() {
public void run() {
doneSearching();
}
}, 1000);
}
}
}
private void doneSearching(){
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
if (devices.size() == 1){
BluetoothDevice device = devices.get(0);
connect(device);
}else{
//normally this displays a list and the user can choose which device. But this works just as well for now.
BluetoothDevice device = devices.get(0);
connect(device);
}
}
//connect method
public boolean connect(BluetoothDevice btdevice) {
orangeDevice = btdevice;
if (mBluetoothAdapter == null || btdevice == null || btdevice.getAddress() == null) {
return false;
}
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
devices = new ArrayList<>();
mBluetoothGatt = orangeDevice.connectGatt(context, true, mGattCallback);
return true;
}
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
foundDevice(device);
}
};
public void setUpCallBack(){
mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
foundDevice(device);
}
#Override
public void onScanFailed(int errorCode) {
Log.e("Chase", "Scan Failed Error Code: " + errorCode);
}
};
}
}
Update
I was playing around with a galaxy tablet and wasn't able to recreate the issue. So I think it's device dependent. The problem occurs on the Galaxy S3, and I am trying to round up some other devices to test with.
Also, I was able to get my hands on some new devices and it seems that if the device has never been connected before to the phone (virgin device) then that device doesn't get mixed up and think it's connected when it's not. So we will see it when we search but it never thinks I am connecting to it, until I actually do connect to it. After that, then half the time it thinks I am trying to talk to it when I am not. I hope that makes sense. Which backs up the theory that the phone is somehow caching old devices. I tried uninstalling the app and reinstalling it to see if it would have the same effect as using a virgin device, but it seems the app has nothing to do with it. The device will still connect (when it's not supposed to) after it has been introduced to the phone, even if I did a fresh install of the app.
I would check the BLE devices themselves. Is there a chance that they might have the same System ID ? I believe it's the first characteristic in 0x180A. If so - it will be difficult for the host to distinguish them and such a double connection might happen.
-Lets say you have 2 devices. So foundDevice() gets called. Now devices arraylist contains 1 device.
-After that you are using handler which calls doneSearching() & checks
if device.size()==1
It returns true and you call connect()
-Inside connect you are again creating an arraylist i.e
devices = new ArrayList<>();
So what happens now is your devices ArrayList<>() contains 0 elements.
-So now when 2nd device is found again the above steps are repeated because whenever connect method is getting called, the size of list is getting refreshed to 0
So just remove the line
devices = new ArrayList<>();
inside connect() method
I am developing a demo app to scan Blutooth Le devices. But startLeScan() returns null. and I am not getting any device name. I have tried using normal scan it shows up fine. I am adding my snippet here
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mTxtInfo.setText("Stopped Scanning.");
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mTxtInfo.setText("Started Scanning...");
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mTxtInfo.setText("Stopped Scanning.");
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
This is my function to start Le scan.
// Device scan callback.
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() {
mTxtInfo.setText("Detected devices: " + device.getName());
Toast.makeText(MainActivity.this,
"Detected devices: " + device.getName(),
Toast.LENGTH_LONG).show();
}
});
}
};
this is the callback. And its showing me
07-04 12:50:17.833: D/BluetoothAdapter(3564): startLeScan(): null
Would appreciate any help.
The Problem there is that you are trying to use
startLeScan(callback)
without setting the Uuid parameter. So the BluetoothAdapter code it's doing something like:
startLeScan(null, callback)
on the back and printing
"startLeScan:" + Uuid.
Which for you is null.
I am keep on getting the NoClassDefFoundError when my class is loaded.
The code is taken from BluetoothLeGatt project -
http://developer.android.com/samples/BluetoothLeGatt/project.html
My code:
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() { //java.lang.NoClassDefFoundError...
#Override
public void onLeScan(final BluetoothDevice device,
final int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run() {
String msg= device.getAddress();
Log.d(TAG,msg);
addItems(msg);
}
});
}
};
Someone suggested that the error is because my device doesn't support BLE but I want to get rid of this error for any device. So if it doesn't support BLE feature then simply skip this error else continue with the call to this BluetoothAdapter.LeScanCallback.
NOTE:
See this my previous SO post for more clarification.
Putting the BLE feature check as the first line onCreate() doesn't stop the crash --
#Override
public void onCreate(Bundle savedInstanceState) {
if (!bleCheck()) {
Toast.makeText(getApplicationContext(), R.string.ble_not_supported,
Toast.LENGTH_SHORT).show();
finish();
}
//Rest of the code
//Call to BLE Scan on button click that causes error..
}
private boolean bleCheck() {
boolean result = false;
if (getPackageManager().
hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
result = true;
}
return result;
}
As BluetoothAdapter.LeScanCallback was Added in API level 18 ; Source, this code would need a API level check also. Here's how i have gone about this, not declared the callback as private but under the condition:
boolean apiJBorAbove = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2 ? true
: false;
boolean isBleAvailable = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE) ? true : false;
// Use this check to determine whether BLE is supported on the device.
if (isBleAvailable && apiJBorAbove) {
// Initializes a Bluetooth adapter.
// For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Ensures Bluetooth is available on the device and it is enabled.
// If not, displays a dialog requesting user permission to enable
// Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
}
BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device,
final int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run() {
String msg= device.getAddress();
// Log.d(TAG,msg);
// addItems(msg);
}
});
}
};
}
The NoClassDefFoundError is due to the API , not on the basis of PackageManager.FEATURE_BLUETOOTH_LE.