Android 12 蓝牙权限混乱
Android 12 bluetooth permissions confusion
所以在API31中有新的蓝牙权限。我想用这个打开或关闭蓝牙:
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" />
Android 文档说如果目标 API 31 或更高,则将 android:maxSdkVersion="30"
添加到上述蓝牙权限。我还在 Android Studio 中收到一个错误,即 enable() 和 disable() 函数需要 "android.permission.BLUETOOTH_CONNECT"
.
如果加上android:maxSdkVersion="30"
告诉系统忽略
更高 APIs 的声明是可选的而不是强制性的,
这是否意味着不添加它将使语句继续工作
更高 APIs?
如果"android.permission.BLUETOOTH_CONNECT"
是为了让我的应用
与其他蓝牙设备交互那么为什么需要
在原始设备上启用或禁用蓝牙适配器?
如果运行时需要申请BLUETOOTH_CONNECT权限
正确的完整方法是什么?意思是检查它是否已经
如果不是,则授予然后请求它。我没有 Android 12 设备所以
无法测试此代码。
目标 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)
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.
是的,以同样的方式在 Android < 12 上请求位置许可(不再需要),但是同时请求 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT
为了改进@AndreasGobs 的回答,请在代码下方根据当前可用权限测试与设备的连接是否可行。
在清单中,我设置了粗略和精细位置权限必须限制为最大 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);
}
所以在API31中有新的蓝牙权限。我想用这个打开或关闭蓝牙:
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" />
Android 文档说如果目标 API 31 或更高,则将 android:maxSdkVersion="30"
添加到上述蓝牙权限。我还在 Android Studio 中收到一个错误,即 enable() 和 disable() 函数需要 "android.permission.BLUETOOTH_CONNECT"
.
如果加上
android:maxSdkVersion="30"
告诉系统忽略 更高 APIs 的声明是可选的而不是强制性的, 这是否意味着不添加它将使语句继续工作 更高 APIs?如果
"android.permission.BLUETOOTH_CONNECT"
是为了让我的应用 与其他蓝牙设备交互那么为什么需要 在原始设备上启用或禁用蓝牙适配器?如果运行时需要申请BLUETOOTH_CONNECT权限 正确的完整方法是什么?意思是检查它是否已经 如果不是,则授予然后请求它。我没有 Android 12 设备所以 无法测试此代码。
目标 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)
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.
是的,以同样的方式在 Android < 12 上请求位置许可(不再需要),但是同时请求 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT
为了改进@AndreasGobs 的回答,请在代码下方根据当前可用权限测试与设备的连接是否可行。 在清单中,我设置了粗略和精细位置权限必须限制为最大 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);
}