- Bluetooth permissions
- Declare permissions
- Target Android 12 or higher
- Strongly assert that your app doesn’t derive physical location
- Target Android 11 or lower
- Discover local Bluetooth devices
- Specify Bluetooth feature usage
- Check feature availability at runtime
- Kotlin
- Java
- New Bluetooth permissions in Android 12
- New permissions
- No location permission?
- Request user approval
- Conclusion
- Android 12 bluetooth permissions confusion
Bluetooth permissions
To use Bluetooth features in your app, you must declare several permissions. You should also specify whether your app requires support for Bluetooth classic or Bluetooth Low Energy (BLE). If your app doesn’t require Bluetooth classic or BLE but can still benefit from these technologies, you can check for availability at runtime.
Declare permissions
The set of permissions that you declare in your app depends on your app’s target SDK version.
Target Android 12 or higher
Note: On Android 8.0 (API level 26) and higher, the Companion Device Manager (CDM) provides a more streamlined method of connecting to companion devices, compared to the permissions described in this section. The CDM system provides a pairing UI on behalf of your app and doesn’t require location permissions.
If you want more control over the pairing and connecting experience, use the permissions described in this section.
If your app targets Android 12 (API level 31) or higher, declare the following permissions in your app’s manifest file:
- If your app looks for Bluetooth devices, such as BLE peripherals, declare the BLUETOOTH_SCAN permission.
- If your app makes the current device discoverable to other Bluetooth devices, declare the BLUETOOTH_ADVERTISE permission.
- If your app communicates with already-paired Bluetooth devices, declare the BLUETOOTH_CONNECT permission.
- For your legacy Bluetooth-related permission declarations, set android:maxSdkVersion to 30 . This app compatibility step helps the system grant your app only the Bluetooth permissions that it needs when installed on devices that run Android 12 or higher.
- If your app uses Bluetooth scan results to derive physical location, declare the ACCESS_FINE_LOCATION permission. Otherwise, you can strongly assert that your app doesn’t derive physical location.
The BLUETOOTH_ADVERTISE , BLUETOOTH_CONNECT , and BLUETOOTH_SCAN permissions are runtime permissions. Therefore, you must explicitly request user approval in your app before you can look for Bluetooth devices, make a device discoverable to other devices, or communicate with already-paired Bluetooth devices. When your app requests at least one of these permissions, the system prompts the user to allow your app to access Nearby devices, as shown in figure 1.
The following code snippet demonstrates how to declare Bluetooth-related permissions in your app if it targets Android 12 or higher:
Strongly assert that your app doesn’t derive physical location
If your app doesn’t use Bluetooth scan results to derive physical location, you can make a strong assertion that your app never uses the Bluetooth permissions to derive physical location. To do so, complete the following steps:
- Add the android:usesPermissionFlags attribute to your BLUETOOTH_SCAN permission declaration, and set this attribute’s value to neverForLocation . Note: If you include neverForLocation in your android:usesPermissionFlags , some BLE beacons are filtered from the scan results.
- If location isn’t otherwise needed for your app, remove the ACCESS_FINE_LOCATION permission from your app’s manifest.
The following code snippet shows how to update your app’s manifest file:
Target Android 11 or lower
If your app targets Android 11 (API level 30) or lower, declare the following permissions in your app’s manifest file:
- BLUETOOTH is necessary to perform any Bluetooth classic or BLE communication, such as requesting a connection, accepting a connection, and transferring data.
- ACCESS_FINE_LOCATION is necessary because, on Android 11 and lower, a Bluetooth scan could potentially be used to gather information about the location of the user.
Because location permissions are runtime permissions, you must request these permissions at runtime along with declaring them in your manifest.
Discover local Bluetooth devices
If you want your app to initiate device discovery or manipulate Bluetooth settings, you must declare the BLUETOOTH_ADMIN permission. Most apps need this permission solely for the ability to discover local Bluetooth devices. Don’t use the other abilities granted by this permission unless the app is a «power manager» that modifies Bluetooth settings upon user request. Declare the permission in your app manifest file. For example:
If your app supports a service and can run on Android 10 (API level 29) or Android 11, you must also declare the ACCESS_BACKGROUND_LOCATION permission to discover Bluetooth devices. For more information on this requirement, see Access location in the background.
The following code snippet shows how to declare the ACCESS_BACKGROUND_LOCATION permission:
Specify Bluetooth feature usage
If Bluetooth is a critical piece of your app, you can add flags to your manifest file indicating this requirement. The element allows you to specify the type of hardware your app uses and whether or not it is required.
This example shows how to indicate that Bluetooth classic is required for your app.
If your app relies on Bluetooth Low Energy, you can use the following:
If you say the feature is required for your app, then the Google Play store will hide your app from users on devices lacking those features. For this reason, you should only set the required attribute to true if your app can’t work without the feature.
Check feature availability at runtime
To make your app available to devices that don’t support Bluetooth classic or BLE, you should still include the element in your app’s manifest, but set required=»false» . Then, at run-time, you can determine feature availability by using PackageManager.hasSystemFeature() :
Kotlin
// Check to see if the Bluetooth classic feature is available. val bluetoothAvailable = packgeManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) // Check to see if the BLE feature is available. val bluetoothLEAvailable = packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
Java
// Use this check to determine whether Bluetooth classic is supported on the device. // Then you can selectively disable BLE-related features. boolean bluetoothAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); // Use this check to determine whether BLE is supported on the device. Then // you can selectively disable BLE-related features. boolean bluetoothLEAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
Last updated 2023-07-14 UTC.
New Bluetooth permissions in Android 12
In this article, I will discuss the new Bluetooth permissions introduced in Android 12.
New permissions
For apps targeting Android 12 or higher, the following three new permissions are introduced:
- BLUETOOTH_CONNECT: required to connect to paired Bluetooth devices.
- BLUETOOTH_SCAN: equired to scan and pair nearby Bluetooth devices.
- BLUETOOTH_ADVERTISE: required to advertise to nearby Bluetooth devices.
For apps that also support older Android versions, we can declare the permissions like this:
. > android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" /> android:name="android.permission.BLUETOOTH_CONNECT" /> android:name="android.permission.BLUETOOTH_SCAN" /> android:name="android.permission.BLUETOOTH_ADVERTISE" /> .
No location permission?
Really? No location permission required now? Well, yes and no.
If the app uses Bluetooth to derive physical location, e.g. through BLE beacons, you still need to declare the ACCESS_FINE_LOCATION permission as before.
However, if the app does not derive physical locations, you can add the android:usesPermissionFlags=»neverForLocation» attribute to the BLUETOOTH_SCAN permission declaration:
. > android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> .
Request user approval
Unfortunately, when you run the app on Android 12 devices, you will get the SecurityException for missing the needed permissions. That’s because the new permissions are runtime permissions that must be approved by the user.
Assume the app doesn’t need to advertise, and doesn’t derive physical locations, we can request the permission like this:
class MainActivity : Activity() companion object private const val BLUETOOTH_PERMISSION_REQUEST_CODE = 9999 > override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) initializeBluetoothOrRequestPermission() ... > private fun initializeBluetoothOrRequestPermission() val requiredPermissions = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) listOf(Manifest.permission.ACCESS_FINE_LOCATION) > else listOf(Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN) > val missingPermissions = requiredPermissions.filter < permission -> checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED > if (missingPermissions.isEmpty()) initializeBluetooth() > else requestPermissions(missingPermissions.toTypedArray(), BLUETOOTH_PERMISSION_REQUEST_CODE) > > private fun initializeBluetooth() < ... > override fun onRequestPermissionsResult(requestCode: Int, permissions: Arrayout String>, grantResults: IntArray) when (requestCode) BLUETOOTH_PERMISSION_REQUEST_CODE -> if (grantResults.none < it != PackageManager.PERMISSION_GRANTED >) // all permissions are granted initializeBluetooth() > else // some permissions are not granted > > else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) > > ... >
Conclusion
This is all you need to use the new Bluetooth permissions for Android 12 and above, and it’s really a great improvement for no longer requiring the location permission for scanning.
Let me know how if you have any questions and happy coding!
Android 12 bluetooth permissions confusion
Targeting Android 12 my working solution is to declare the permissions in this way:
Like you said, BLUETOOTH_SCAN is not sufficient and you need BLUETOOTH_CONNECT (also if you decide, like me, to ask to the user to enable Bluetooth starting a new startActivityForResult with action BluetoothAdapter.ACTION_REQUEST_ENABLE)
If the BLUETOOTH_CONNECT permission needs to be requested at runtime what is the correct full way to do it? Meaning checking if it’s already granted then requesting it if it’s not. I have no Android 12 device so no way to test this code.
To improve @AndreasGobs answer, below the code to test if the connection with a device is viable or not based on current available permissions. In the manifest I’ve set that the COARSE and FINE location permissions must be limited to max API 30. Tested on Android 6, 8.1, 11 and 12 devices. I hope this will be useful.
/** * - API < S * - Check ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions * - API < O * - Check has GPS * - Check GPS enabled * - API >= S * - Check BLUETOOTH_SCAN permission * - Check BLUETOOTH_CONNECT permission * - Check Bluetooth enabled */ private boolean canConnect() < Timber.d("canConnect called"); ListdeniedPermissions = new ArrayList<>(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) < if (!checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) deniedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); if (!checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) deniedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); if(deniedPermissions.isEmpty())< if (!MmcDeviceCapabilities.hasLocationGps() //check if the device has GPS || Build.VERSION.SDK_INT < Build.VERSION_CODES.O || MmcDeviceCapabilities.isGpsEnabled())< //check if the GPS is enabled if(MmcDeviceCapabilities.bluetoothEnabled()) //check if bluetooth is enabled return true; else < requestEnableBluetooth(); //method to request enable bluetooth return false; >> else < Timber.d("Request enable GPS"); requestEnableGps(); //method to request enable GPS (improving devices scan) return false; >> else < Timber.d("Request GPS permissions"); requestRuntimePermissions( "Bluetooth GPS request", "GPS permissions request rationale", GPS_PERMISSIONS_CODE, deniedPermissions.toArray(new String[0])); return false; >> else < // Build.VERSION_CODES.S or later if(!checkPermission(Manifest.permission.BLUETOOTH_SCAN)) deniedPermissions.add(Manifest.permission.BLUETOOTH_SCAN); if(!checkPermission(Manifest.permission.BLUETOOTH_CONNECT)) deniedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT); if(deniedPermissions.isEmpty()) if(MmcDeviceCapabilities.bluetoothEnabled()) //check if bluetooth is enabled return true; else < requestEnableBluetooth(); //method to request enable bluetooth return false; >else < Timber.d("Request bluetooth permissions"); requestRuntimePermissions( "Bluetooth permissions request", "Bluetooth permissions request rationale", CONNECT_PERMISSIONS_CODE, deniedPermissions.toArray(new String[0])); return false; >> > /** * This method checks if a runtime permission has been granted. * @param permission The permission to check. * @return TRUE
if the permission has been granted, FALSE
otherwise. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean checkPermission(@NonNull String permission) < return ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED; >private void requestRuntimePermissions(@NonNull String title, @NonNull String description, int requestCode, @NonNull String. permissions) < if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) < AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder .setTitle(title) .setMessage(description) .setCancelable(false) .setNegativeButton(android.R.string.no, (dialog, id) ->< //do nothing >) .setPositiveButton(android.R.string.ok, (dialog, id) -> ActivityCompat.requestPermissions(this, permissions, requestCode)); showDialog(builder); //method to show a dialog > else ActivityCompat.requestPermissions(this, permissions, requestCode); >