简体   繁体   中英

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 (ie 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.

First you should check if your app was granted the location permission(s) in the Settings app > Apps <your_app> > permissions. Some permissions (like ACCESS_*_LOCATION and BLUETOOTH_ADMIN) need to be requested at runtime and granted by the user through a popup. Normally you should get a SecurityException or a logcat warning when trying to execute code requiring permissions which your app doesn't have, but it's not uncommon for android to skip over error handling.

Consider using this method to start the scan in order check its result code for potential additional info about what is (not) going on.

You might also get some clues by logging all actions received in BTReceiver.onReceive(), not just action found.

Lastly check if the location settings on your device to ensure that bluetooth scanning is turned on (Settings app > location > wifi and bluetooth scanning )

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM