Skip to content

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:

xml
<!-- 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:

xml
<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:

kotlin
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)
    }
}
java
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:

kotlin
@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:

kotlin
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:

xml
<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:

kotlin
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

  1. Always check permissions before performing Bluetooth operations
  2. Use the neverForLocation flag when appropriate to avoid unnecessary location permission requests
  3. Handle permission denials gracefully with user education
  4. Test on multiple Android versions to ensure compatibility
  5. 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:

  1. Declare all necessary permissions in your manifest
  2. Request runtime permissions for Android 12+
  3. Handle different Android versions appropriately
  4. Check permissions before Bluetooth operations
  5. 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.

Additional Resources