javaandroidbluetoothbluetooth-lowenergy

Unable to scan or discover BT and BLE devices in android


I am a complete novice in Java and Android. I am trying to create a test app to listen for BLE and BT devices nearby. I have another device where I wrote some logic to broadcast its BLE beacons. I verified it using a playstore app. Now I am trying to write my own app on Android. I have been reading the Android developer pages for guidance. I have literally followed every step of the following pages

https://developer.android.com/guide/topics/connectivity/bluetooth/setup

https://developer.android.com/guide/topics/connectivity/bluetooth/permissions

https://developer.android.com/guide/topics/connectivity/bluetooth/find-bluetooth-devices

https://developer.android.com/guide/topics/connectivity/bluetooth/find-ble-devices

Also, Note that I have used BARE MINIMUM CODE from the Android Developers page So here is what I have done.

1. First off I have added my permissions under AndroidManifest

Note1 : I am deploying this app to My phone running Android 11

Note2 : All this code is written inside MainActivity. I have not created any other activity class

<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

2. Next I check if my BT is enabled.

        if (bluetoothAdapter == null) {
            blefinder.append("\nDEVICE DOES NOT SUPPORT BLUETOOTH");
        }
        else {
            blefinder.append("\nDEVICE SUPPORTS BLUETOOTH");
        }

I get the success message that BT is of course enabled

3. Next I check if my device supports BLE

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            blefinder.append("\nBLE NOT SUPPORTED ON THIS DEVICE : ");
            finish();
        }
        else{
            blefinder.append("\nBLE IS SUPPORTED ON THIS DEVICE : ");
        }

I get the message that BLE is supported

4. Next I list my already paired/bonded devices

For this I call ListPairedAndBondedDevices(); in onCreate() itself right after the above steps. Function Definition Below.

private void ListPairedAndBondedDevices(){
        @SuppressLint("MissingPermission") Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();

        if (pairedDevices.size() > 0) {
            // There are paired devices. Get the name and address of each paired device.
            blefinder.append("\nPAIRED/BONDED DEVICES");
            for (BluetoothDevice device : pairedDevices) {
                blefinder.append("\n" + device.getName() + " | " + device.getAddress());
            }
        }
    }

This also works like a charm and prints out my paired devices. The next 2 parts is where I face the problem.

5. The Problem Step | Part 1:

Here I register a Broadcast receiver to discover all BT devices in the vicinity. I've unbonded my BT headphones and kept it in pairing mode to verify this.

ListPairedAndBondedDevices(); // From previous code snippet
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); // New code statement 
registerReceiver(BTReceiver, filter);// New code statement 

Broadcast Receiver implementation

    private final BroadcastReceiver BTReceiver = new BroadcastReceiver() {
        @SuppressLint("MissingPermission")
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Discovery has found a device. Get the BluetoothDevice
                // object and its info from the Intent.
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                blefinder.append("\n" + device.getName() + " | " + device.getAddress());

            }
        }
    };

So This part didn't Work :(

If you see above, I am registering the BTReceiver in onCreate right after listing the already paired devices (by calling ListPairedAndBondedDevices()).

When I ran the debugger, this broadcast receiver never gets called.

6. The Problem Step | Part 2:

Right after this I try to scan for BLE Devices as well by callin scanLeDevice()

ListPairedAndBondedDevices(); // From previous snippet
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); // From previous snippet
registerReceiver(BTReceiver, filter);// From previous snippet
scanLeDevice(); // ---------------->>> CALLING THIS FUNCTION TO SCAN FOR BLE DEVICES

Implementation of scanLeDevice()

    private void scanLeDevice() {
        if (!scanning) {
            // Stops scanning after a predefined scan period.
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    scanning = false;
                    bluetoothLeScanner.stopScan(leScanCallback);
                    blefinder.append("\nSTOPPING BLE SCAN... TIMEOUT REACHED");
                }
            }, SCAN_PERIOD);

            scanning = true;
            bluetoothLeScanner.startScan(leScanCallback);
        } else {
            scanning = false;
            bluetoothLeScanner.stopScan(leScanCallback);
            blefinder.append("\nSTOPPING BLE SCAN");
        }
    }

Unfortunately this also fails. The debugger tells me that this part of the code is getting called. And after 30 seconds of SCAN_PERIOD (The TIMEOUT that I've set), I get the message that the scanning has stopped (STOPPING BLE SCAN)

Now I have implemented the leScanCallback as well (i.e the Device Scan Callback)

    private ScanCallback leScanCallback =
            new ScanCallback() {
                @Override
                public void onScanResult(int callbackType, ScanResult result) {
                    super.onScanResult(callbackType, result);
                    blefinder.append("SOMETHING GOT SCANNED?");
                    blefinder.append("\n"+result.getDevice().toString());
                //  leDeviceListAdapter.addDevice(result.getDevice());
                //  leDeviceListAdapter.notifyDataSetChanged();
                }
            };

Notice that I am not using a ListAdapter since I have no idea about that concept. Hence for starters I am just trying to dump the results in a TextView represented by blefinder . This blefinder prints all the other texts so there is nothing wrong with that TextView variable. When I ran using the, debugger, it is not entering into the leScanCallback piece of code definition at all, even after 30 seconds, after scanLeDevice() function is executed.

I am a little lost here. Is there something I may be missing or doing wrong. It is supposed to be a simple, list the ble/bt devices around my vicinity.

I am happy to share any further information if I have missed. Just let me know in the comments.


Solution

  • Assuming you've done with the permissions that I've mentioned in the comments, we can implement a clean bluetooth LE scanner object and then use it in the UI.

    First we implement a result consumer interface in order to deliver the results to the consumers which call the BleScanner.scan() method.

    public interface ScanResultConsumer {
    
        public void onDeviceFound(BluetoothDevice device, byte[] scanRecord, int rssi);
        public void onScanningStarted();
        public void onScanningStopped();
    }
    

    Now we need to implement the scanner object that manages the scanning events:

    public class BleScanner {
        private static final String TAG = BleScanner.class.getSimpleName();
    
        private BluetoothLeScanner leScanner = null;
        private BluetoothAdapter bleAdapter = null;
        private Handler uiHandler = new Handler(Looper.getMainLooper);
        private ScanResultConsumer scanResultConsumer;
        private boolean scanning = false;
        private final ArrayList<BluetoothDevice> foundDeviceList = new ArrayList<>();
    
        public BleScanner(Context context) {
            final BluetoothManager bluetoothManager = (BluetoothManager)
                    context.getSystemService(Context.BLUETOOTH_SERVICE);
    
            bleAdapter = bluetoothManager.getAdapter();
            if(bleAdapter == null) { 
                Log.d(TAG, "No bluetooth hardware.");
            }
            else if(!bleAdapter.isEnabled()){
                Log.d(TAG, "Blutooth is off.");
            }
        }
    
        public void scan(ScanResultConsumer scanResultConsumer, long scanTime){
            foundDeviceList.clear();
            if (scanning){
                Log.d(TAG, "Already scanning.");
                return;
            }
            Log.d(TAG, "Scanning...");
            if(leScanner == null){
                leScanner = bleAdapter.getBluetoothLeScanner();
            }
    
            if(scanTimeMs > 0) {
                uiHandler.postDelayed(()-> {
                    if (scanning) {
                        Log.d(TAG, "Scanning is stopping.");
                        if(leScanner != null)
                            leScanner.stopScan(scanCallBack);
                        else
                            Log.d(TAG,"Scanner null");
                        setScanning(false);
                    }
                }, scanTimeMs);
            }
    
            this.scanResultConsumer = scanResultConsumer;
            leScanner.startScan(scanCallBack);
            setScanning(true);
        }
    
    
        private final ScanCallback scanCallBack = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
                if (!scanning){
                    return;
                }
    
                if(foundDeviceList.contains(result.getDevice())) {
                    // This device has already been found
                    return;
                }
    
                // New device found, add it to the list in order to prevent duplications
                foundDeviceList.add(result.getDevice());
    
                if(scanResultConsumer != null) {
                    uiHandler.post(() -> {
                        scanResultConsumer.onDeviceFound(result.getDevice(),
                                result.getScanRecord().getBytes(), result.getRssi());
                    });
                }
            }
        };
    
        public boolean isScanning(){
            return scanning;
        }
    
        void setScanning(boolean scanning){
            this.scanning = scanning;
            uiHandler.post(() -> {
                if(scanResultConsumer == null) return;
                if(!scanning){
                    scanResultConsumer.onScanningStopped();
                    // Nullify the consumer in order to prevent UI crashes
                    scanResultConsumer = null;
                } else{
                    scanResultConsumer.onScanningStarted();
                }
            });
        }
    }
    

    Finally we can use this clean implementation in anywhere we need. But do note that a context must be provided in order to create a BleScanner object.

    public class MainActivity extends AppCompatActivity {
    
        private BleScanner bleScanner;
        private Button buttonScan
        // Other codes...
    
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // Other codes...
            bleScanner = new BleScanner(getApplicationContext());
            // Other codes...
    
            // For example if you want to start scanning on a button press
            // Let's say you have a button called buttonScan and initiated it
            buttonScan = findViewById(R.id.scan_button);
    
            buttonScan.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    bleScanner.scan(new ScanResultConsumer {
    
                        @Override
                        public void onDeviceFound(BluetoothDevice device, byte[] scanRecord, int rssi) {
                            // TODO Here you have a newly found device, do something.
                        }
    
                        @Override
            q           public void onScanningStarted() {
                            // TODO Scanning has just started, you may want to make some UI changes.
                        }
    
                        @Override
                        public void onScanningStopped() {
                            // TODO Scanning has just stopped, you may want to make some UI changes.
                        }
                    });
                }
            });
        }
    
    }
    

    Note: I written this code in a plain editor not in Android Studio. So there may be some errors, let me know if any.