Search code examples
androidbluetoothbluetooth-lowenergymaui

MAUI application not scanning BLE


In my MAUI application, I need to scan for BLE devices. My code is working perfectly on Windows, however on Android, the scan does not discover any devices. I know I have to declare required permissions in manifest and interactively ask for them.

My minimum example for C#:

using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;

namespace BLE2
{
    public partial class MainPage : ContentPage
    {
        private IAdapter adapter;
        private IBluetoothLE ble;
        private List<IDevice> devices = [];

        public MainPage()
        {
            InitializeComponent();

            ble = CrossBluetoothLE.Current;
            adapter = ble.Adapter;
            adapter.ScanTimeout = 3000;
            adapter.ScanMode = ScanMode.Balanced;

            adapter.DeviceDiscovered += (sender, eventArgs) =>
            {
                devices.Add(eventArgs.Device);
            };
        }

        private void OnScanBtnClicked(object sender, EventArgs e)
        {
            adapter.StartScanningForDevicesAsync();
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();
            await RequestPermissionsAsync();
        }

        async Task RequestPermissionsAsync()
        {
            var locationPermitStatus = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
            if (locationPermitStatus != PermissionStatus.Granted)
            {
                await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
            }

            var bluetoothPermitStatus = await Permissions.CheckStatusAsync<Permissions.Bluetooth>();
            if (bluetoothPermitStatus != PermissionStatus.Granted)
            {
                await Permissions.RequestAsync<Permissions.Bluetooth>();
            }
        }
    }

}

and the AnddoidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-sdk />
</manifest>

"Watching" properties while debugging shows that ale permissions are granted. However, the event adapter.deviceDiscovered is never fired (again tested with a breakpoint).

What have I missed?


Solution

  • I created a new project with your code and tested it on the physical device with Android 14.0. But the app will crash when I clicked the button. I found the cause is the permission after I checked the nuget package github repo:

    You can check the part about the Android permission:

    Android 12 and above may require one or more of the following additional runtime permissions, depending on which features of the library you are using (see the android Bluetooth permissions documentation)

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    

    The adapter.deviceDiscovered was fired after I added the permission above into the AndroidManifest.

    First of all, you can read the official document about bluetooth permission on the android. Just add the following code into the AndroidManifest.xml:

        <!-- Request legacy Bluetooth permissions on older devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH"
                         android:maxSdkVersion="30" />
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                         android:maxSdkVersion="30" />
    
        <!-- Needed only if your app looks for Bluetooth devices.
             If your app doesn't use Bluetooth scan results to derive physical
             location information, you can
             <a href="#assert-never-for-location">strongly assert that your app
             doesn't derive physical location</a>. -->
        <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    
        <!-- Needed only if your app makes the device discoverable to Bluetooth
             devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    
        <!-- Needed only if your app communicates with already-paired Bluetooth
             devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    
        <!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        ...
    

    And then, you can use the following condition code to request permission at run time instead of #if ANDROID31_0_OR_GREATER:

    // this will called when the api is android 31 0r heigher
    if (Android.OS.Build.VERSION > Android.OS.BuildVersionCodes.R)
    {
        // request permission
    }else{}
    

    Actually, you don't have to do this. Just adding the permssion into the AndroidManifest.xml and requesting permission with Permissions.RequestAsync<Permissions.Bluetooth>() are ok. You don't have to consider the different versions setting.