Android 12 Bluetooth Permissions
With the release of Android 12, Google introduced significant changes to Bluetooth permissions that have impacted many developers. If you're experiencing issues with Bluetooth functionality after targeting Android 12, this guide will help you implement the proper permission structure.
Problem Statement
Starting with Android 12 (API level 31), the Bluetooth permission model was overhauled to provide users with more granular control over what Bluetooth operations apps can perform. Developers who only added the new BLUETOOTH_SCAN
and BLUETOOTH_CONNECT
permissions without proper runtime handling found their apps unable to discover or connect to Bluetooth devices.
The core issue stems from:
- New runtime permissions requiring explicit user approval
- Different permission requirements based on Android version
- Potential need for location permissions in certain scenarios
- Incorrect manifest declarations
Complete Solution
Manifest Configuration
First, ensure your AndroidManifest.xml
includes all necessary permissions and features:
<!-- Legacy Bluetooth permissions for older devices -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Optional: Location permissions if needed for scanning -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Optional: Nearby devices permission -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<!-- Hardware features (set required="false" for optional features) -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
Manifest Optimization
If your app doesn't use Bluetooth scanning to derive physical location information, add the neverForLocation
flag to avoid needing location permissions:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s"/>
Runtime Permission Handling
For Android 12 and above, you must request Bluetooth permissions at runtime:
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
Log.d("Bluetooth", "${it.key} = ${it.value}")
}
}
fun requestBluetoothPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestMultiplePermissions.launch(
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
)
)
} else {
// Handle pre-Android 12 Bluetooth enabling
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
}
private static final int REQUEST_BLUETOOTH_PERMISSIONS = 100;
private void requestBluetoothPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
},
REQUEST_BLUETOOTH_PERMISSIONS);
}
} else {
// Pre-Android 12 handling
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
Jetpack Compose Implementation
If using Jetpack Compose, handle permissions with the following approach:
@Composable
fun BluetoothPermissionHandler() {
val permissionsState = rememberMultiplePermissionsState(
permissions = listOf(
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
)
)
// Request permissions when composable starts
LaunchedEffect(Unit) {
permissionsState.launchMultiplePermissionRequest()
}
// Observe permission states
permissionsState.permissions.forEach { permissionState ->
when(permissionState.permission) {
Manifest.permission.BLUETOOTH_CONNECT -> {
if (permissionState.status.isGranted) {
// Permission granted, proceed with Bluetooth operations
}
}
Manifest.permission.BLUETOOTH_SCAN -> {
if (permissionState.status.isGranted) {
// Permission granted, proceed with scanning
}
}
}
}
}
Permission Check Before Bluetooth Operations
Always verify permissions before performing Bluetooth operations:
fun checkBluetoothPermissions(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_SCAN
) == PackageManager.PERMISSION_GRANTED
} else {
// For older versions, check traditional permissions
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
}
Common Issues and Solutions
Nearby Devices Permission
On some Android 12+ devices, you may need to manually enable the "Nearby devices" permission in app settings:
Manual Permission Grant
Some users reported that even with proper code implementation, they needed to manually enable "Nearby devices" permission in their app settings for Bluetooth to work correctly on Android 12 and 13.
Background Location Considerations
If your app needs to scan for Bluetooth devices in the background, you'll need additional permissions:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
However, Google Play policies restrict background location access to apps with legitimate use cases.
Android Version Compatibility
Ensure your code handles different Android versions appropriately:
fun setupBluetooth() {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
// Android 12+ implementation
requestBluetoothPermissions()
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
// Android 6.0-11 implementation
requestLocationPermissions()
enableBluetooth()
}
else -> {
// Pre-Android 6.0 implementation
enableBluetooth()
}
}
}
Best Practices
- Always check permissions before performing Bluetooth operations
- Use the neverForLocation flag when appropriate to avoid unnecessary location permission requests
- Handle permission denials gracefully with user education
- Test on multiple Android versions to ensure compatibility
- Follow the principle of least privilege - only request permissions you actually need
Deprecated Methods
Avoid using the deprecated requestPermissions()
method in favor of the newer Activity Result APIs, which provide better separation of concerns and testing capabilities.
Conclusion
Implementing Bluetooth permissions for Android 12+ requires both manifest declarations and runtime permission handling. The key is to:
- Declare all necessary permissions in your manifest
- Request runtime permissions for Android 12+
- Handle different Android versions appropriately
- Check permissions before Bluetooth operations
- Consider using the
neverForLocation
flag to simplify permission requirements
By following these guidelines, you can ensure your app works correctly across all Android versions while respecting user privacy and the new permission model.