Search code examples
androidbluetooth-lowenergyandroid-ble

Android BLE Scan never finds devices


Since a few days I try to implement an BLE connection in my APP. I know that the Device, I try to connect with, is full functional, so the problem must be my code.

I use the BluetoothLeScanner.startScan() method.
But the callback Method is never called.

   public void startScan() {
        if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
            isScanning = true;
            Handler mHandler = new Handler();
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mainActivityHandler.setMSG("Scan stopped");
                    isScanning = false;
                    leScanner.stopScan(scanCallback);
                }
            }, SCAN_TIME);

            mainActivityHandler.setMSG("Start scan");

            try {

                leScanner.startScan(scanCallback);
            } catch (Exception e) {
                mainActivityHandler.catchError(e);
            }

        } else mainActivityHandler.catchError(new Exception("Bluetooth not activated"));
    }

My CallbackMethod (dont know if I use gatt correctly, but this is a other question):

    private ScanCallback scanCallback = new ScanCallback() {


    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        mainActivityHandler.setMSG("Callback");
        isScanning = false;
        try {

            mainActivityHandler.setMSG("Connected to " + results.get(0).getDevice().getName());
            gatt = results.get(0).getDevice().connectGatt(mainActivity, true, bluetoothGattCallback);

            BluetoothGattDescriptor descriptor;
            for (int i = 0; i < charIDs.length; i++) {
                gatt.setCharacteristicNotification(gatt.getService(serviceID[0]).getCharacteristic(charIDs[i]), true);

                descriptor = gatt.getService(serviceID[0]).getCharacteristic(charIDs[0]).getDescriptor(charIDs[i]);
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                gatt.writeDescriptor(descriptor);
            }
        } catch (Exception e) {
            mainActivityHandler.catchError(e);
        }

    }
};

Solution

  • The Problem

    The problem is that you are only overriding the onBatchScanResults method and not onScanResult method. onBatchScanResults will only get triggered if:

    1. You have set the ScanSettings mode to ScanSettings.SCAN_MODE_LOW_POWER (this is the default) using the ScanSettings.Builder.
    2. You have set the report delay time to some value >0 using setReportDelay(long reportDelayMillis) in your ScanSettings.Builder.

    reportDelayMillis - Delay of report in milliseconds. Set to 0 to be notified of results immediately. Values > 0 causes the scan results to be queued up and delivered after the requested delay or when the internal buffers fill up.

    For example:

    public void startScan(BluetoothLeScanner scanner) {
        ScanFilter filter = new ScanFilter.Builder().setDeviceName(null).build();
    
        ArrayList<ScanFilter> filters = new ArrayList<ScanFilter>();
                        filters.add(filter);
    
        ScanSettings settings = new ScanSettings.Builder()
                                    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                                    .setReportDelay(1l)
                                    .build();
    
        Log.i(TAG,"The setting are "+settings.getReportDelayMillis());
        scanner.startScan(filters,settings,BLEScan);
    }
    

    The Solution

    However, you probably just want to get the results once at a time and not a batch of results. To accomplish that, you don't have to modify the ScanSettings, you just need to override the onScanResult method in your ScanCallback and that will do the trick.

    private ScanCallback mScanCallback =
            new ScanCallback() {
    
        public void onScanResult(int callbackType, ScanResult result) {
            System.out.println(result.getDevice().getName())
            // Do whatever you want
        };
    
        ...
    };
    

    The Alternative - RxAndroidBle

    Anyway, I highly recommend using the library RxAndroidBle. It is very well maintained and it solves many of the BLE issues out of the box (scanning is maybe the less complicated part in BLE).

    Using that library, the scanning can be done like this:

    Disposable scanSubscription = rxBleClient.scanBleDevices(
    
    
        new ScanSettings.Builder()
                // .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
                // .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
                .build()
            // add filters if needed
    )
        .subscribe(
            scanResult -> {
                // Process scan result here.
            },
            throwable -> {
                // Handle an error here.
            }
        );
    
    // When done, just dispose.
    scanSubscription.dispose();