What changed
| Target SDK | Scan | Connect / GATT |
|---|---|---|
| Android 12 (API 31)+ | BLUETOOTH_SCAN (runtime) |
BLUETOOTH_CONNECT (runtime) |
| Android 11 and lower | BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION |
BLUETOOTH |
If the app derives physical location from scan results, it still needs ACCESS_FINE_LOCATION on Android 12+.
Manifest that works on both
<!-- Android 11 and lower -->
<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.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<!-- Android 12 and higher -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Request BLUETOOTH_SCAN and BLUETOOTH_CONNECT at runtime before scanning or connecting.
The trap: neverForLocation hides beacons
With neverForLocation, Android filters some BLE beacons out of scan results. That's fine for an app that talks to its own peripheral. It breaks beacon, asset-tracking and "find nearby tags" apps without an error: they simply see fewer devices.
If you need beacons: drop neverForLocation and request ACCESS_FINE_LOCATION as well.
Symptoms of getting it wrong
SecurityExceptionwhen callingconnectGatt()orgetName()withoutBLUETOOTH_CONNECT.- Scans that return nothing, silently, when a permission or Location Services is missing on older versions.
- Works on the developer's phone, fails on another Android version: test on 11 and 12+.