安卓12蓝牙权限混乱

12

在 API 31 中有新的蓝牙权限。我希望使用以下代码打开或关闭蓝牙:

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

在清单文件中我已经添加了如下内容:

<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" />

如果目标API为31或更高版本,则Android文档建议在上述蓝牙权限中添加android:maxSdkVersion="30"。在Android Studio中,enable()disable() 函数需要"android.permission.BLUETOOTH_CONNECT",否则会出现错误。

  1. 如果不将android:maxSdkVersion="30"添加到蓝牙权限中以告诉系统忽略高版本API上的语句是可选而非强制性的,那么这是否意味着如果不添加它,语句也可以在高版本API上工作?

  2. 如果"android.permission.BLUETOOTH_CONNECT"是用来允许我的应用程序与其他蓝牙设备交互的,那么为什么还需要在原始设备上启用或禁用蓝牙适配器?

  3. 如果需要在运行时请求BLUETOOTH_CONNECT权限,正确的完整方法是什么? 意思是检查是否已授予该权限,如果未授予该权限则请求它。我没有Android 12设备,所以无法测试此代码。


如何在按钮点击时禁用蓝牙? - Abhi S
5个回答

13

针对Android 12,我的解决方案是以这种方式声明权限:

<!--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" />

就像你所说的那样,BLUETOOTH_SCAN不足以满足需求,你需要BLUETOOTH_CONNECT(如果你决定像我一样要求用户启用蓝牙,则启动一个新的startActivityForResult并使用BluetoothAdapter.ACTION_REQUEST_ENABLE操作)

如果需要在运行时请求BLUETOOTH_CONNECT权限,正确的完整方式是什么? 即检查是否已授予该权限,然后在没有授权时请求它。 我没有Android 12设备,因此无法测试此代码。

是的,在Android<12上询问位置权限的方式相同(不再需要),现在您需要请求BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限。


启用适配器是否需要“BLUETOOTH_SCAN”权限,还是只需要“BLUETOOTH_CONNECT”权限? - user15603244
我还没有尝试过,但可能BLUETOOTH_CONNECT就足够了。 - AndreaGobs
Android 13 没有更新 ;) - AndreaGobs

1

在 Android 12 上,使用 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" />

1
为了改进@AndreasGobs的答案,以下是测试与设备连接是否可行的代码,基于当前可用的权限。在清单文件中,我设置了COARSE和FINE位置权限必须限制在最大API 30以下。在Android 6、8.1、11和12设备上进行了测试。希望这对你有用。
/**
 * - 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);
}

2
什么是MmcDeviceCapabilities? - Md Tariqul Islam
这是一个拥有外设管理器的类,检查设备中GPS/蓝牙是否可用并且已启用。 - Akamaccio

0

Android 12+需要在运行时获取蓝牙权限。

以下是一个Kotlin示例,用于获取使设备可被发现的蓝牙运行时权限...

 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) {
        fun hasPermissions(context: Context, vararg permissions: String): Boolean = permissions.all {
            ActivityCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
        }
        val permissionAll = 1
        val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            arrayOf(
                Manifest.permission.BLUETOOTH_ADVERTISE,
                Manifest.permission.BLUETOOTH_CONNECT,
                Manifest.permission.BLUETOOTH_SCAN
            )
        } else {
            TODO("VERSION.SDK_INT < S")
        }

        if (!hasPermissions(this, *permissions)) {
            ActivityCompat.requestPermissions(this, permissions, permissionAll)
        }

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

0
我正在使用React Native 0.64版本,并且在Android 12+上遇到了一些类似的问题。
我的清单文件。
<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" />

但是这还不够,我仍然遇到了一个要求蓝牙权限的错误。
我发现 BLUETOOTH_SCAN 是导致问题的原因。 所以在请求了该权限之后,我又能够用我的应用程序重新发现蓝牙设备了。
import {
  requestMultiple,
  PERMISSIONS,
  RESULTS,
} from "react-native-permissions";

然后我有这个助手
let androidPermissions = [PERMISSIONS.ANDROID.BLUETOOTH_SCAN];

const isAndroid12OrGreater = await IS_ANDROID_12_OR_GREATER(); // <-- helper I created to check Android version
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接