Android 12 bluetooth permissions confusion
Asked Answered
R

5

12

So in API 31 there are new Bluetooth permissions. I want to turn bluetooth on or off using this:

private void changeBluetoothState(boolean status) {
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (status)
        mBluetoothAdapter.enable();
    else mBluetoothAdapter.disable();
}

and in manifest I already have this:

<uses-permission
    android:name="android.permission.WRITE_SETTINGS"
    tools:ignore="ProtectedPermissions" />

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

<uses-feature
    android:name="android.hardware.bluetooth"
    android:required="false" />

Android documentations says to add android:maxSdkVersion="30" to the above bluetooth permissions if targeting API 31 or higher. I also get an error in Android Studio that the enable() and disable() functions need "android.permission.BLUETOOTH_CONNECT".

  1. If adding android:maxSdkVersion="30" to tell the system to ignore the statement on higher APIs is optional rather than obligatory, does that mean that not adding it will allow the statement to work on higher APIs?

  2. If "android.permission.BLUETOOTH_CONNECT" is to allow my app to interact with other bluetooth devices then why is it needed to enable or disable the bluetooth adapter on the original device?

  3. 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.

Rhodian answered 27/1, 2022 at 17:34 Comment(0)
G
13

Targeting Android 12 my working solution is to declare the permissions in this way:

<!--Before Android 12 (but still needed location, even if not requested)-->
<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" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!--From Android 12-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

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.

Yes, in the same way of asking location permission on Android < 12 (not need anymore) now you ask for both BLUETOOTH_SCAN and BLUETOOTH_CONNECT

Gaeta answered 25/2, 2022 at 12:57 Comment(3)
Is the BLUETOOTH_SCAN permission needed to enable the adapter or just BLUETOOTH_CONNECT?Rhodian
I haven't try yet but probably BLUETOOTH_CONNECT is sufficientGaeta
There are no updates with Android 13 ;)Gaeta
C
1

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");
    List<String> deniedPermissions = 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 <code>TRUE</code> if the permission has been granted, <code>FALSE</code> 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);
}
Cappuccino answered 30/5, 2022 at 10:41 Comment(2)
what is MmcDeviceCapabilitiesMelamie
It's a class that owns the peripherical managers and checks if the GPS/Bluetooth is available in the device and it's enabled or not.Cappuccino
B
1

This is working for me on Android 12 with React Native 0.68.5

    <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.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Bucephalus answered 29/11, 2022 at 13:54 Comment(0)
B
0

Android 12+ requires runtime permissions for Bluetooth.

Here is a Kotlin example of a Bluetooth runtime permission for making the device discoverable...

 val activityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result: ActivityResult ->
        if (result.resultCode == RESULT_OK) {
            // There are no request codes
            result.data
        } else {
            // Do something
        }
    }

 if (bluetoothAdapter?.isEnabled == true) {

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

            if (ContextCompat.checkSelfPermission(
                     context,
                     Manifest.permission.BLUETOOTH_CONNECT
                   ) == PackageManager.PERMISSION_GRANTED

                ) {
                     Log.d(tag,"checkSelfPermission")
                     val intentDiscover = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
                     activityResultLauncher.launch(intentDiscover)
                }    
   } else {

       val intentDiscover = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
       activityResultLauncher.launch(intentDiscover)
   }

   
 }
Brooder answered 11/4, 2023 at 7:21 Comment(0)
S
0

I'm using React Native 0.64, and I had some similar issues with Android 12+

My Manifest file

<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.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

But still this wasn't enough, I was still getting and error asking for Bluetooth permissions.

I found out BLUETOOTH_SCAN was the one causing me the problems. So after asking permissions for it I was able to discover bluetooth devices again with my app

And then I have this helper

import DeviceInfo from "react-native-device-info";
import { Platform } from "react-native";
import {
  requestMultiple,
  PERMISSIONS,
  RESULTS,
} from "react-native-permissions";

const PLATFORM = {
  android: "android",
  ios: "ios",
};

export const ANDROID_BUILD_VERSION_S = 31;
export const isAndroid = Platform.OS === PLATFORM.android;

export const IS_ANDROID_12_OR_GREATER = async () => // <-- helper I created to check Android version
  await DeviceInfo.getApiLevel().then(data => data >= 
ANDROID_BUILD_VERSION_S);

let androidPermissions = [PERMISSIONS.ANDROID.BLUETOOTH_SCAN];

const isAndroid12OrGreater = await IS_ANDROID_12_OR_GREATER(); 
if (isAndroid && isAndroid12OrGreater)
  android.push(PERMISSIONS.ANDROID.BLUETOOTH_SCAN); // <-- this one solved the issue in my case

const statuses = await requestMultiple(androidPermissions);
        
return Object.values(statuses).some(el => el === RESULTS.GRANTED); // and then I do some stuff with the results
Shrewsbury answered 1/12, 2023 at 15:42 Comment(2)
Where is the android array? where is the IS_ANDROID_12_OR_GREATER() helper?Kynan
updated my answer. But still don't know which android array you refer toShrewsbury

© 2022 - 2024 — McMap. All rights reserved.