Android:BLE的权限检查[已关闭]

wa7juj8i  于 2023-04-28  发布在  Android
关注(0)|答案(2)|浏览(138)

已关闭,该问题需要details or clarity,目前不接受回答。
**想要改进此问题?**通过editing this post添加详细信息并澄清问题。

6天前关闭。
Improve this question
要在Android应用程序中使用低功耗蓝牙,需要在Manifest中包含多个权限,并在运行时检查用户是否已批准这些权限
问题是有6个可能的权限,但试图检查所有的权限将失败!这是因为运行时权限从来没有授予某些版本的Android。
这6个权限是

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

非常感谢@Kozmotronik提供的简单解决方案,只需检查每个版本Android所需的权限
以下代码检查所需的权限,如果尚未授予权限,则将其添加到列表中。然后请求缺少的权限。

private fun checkAndRequestMissingPermissions() {
    // check required permissions - request those which have not already been granted
    val missingPermissionsToBeRequested = ArrayList<String>()
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED)
        missingPermissionsToBeRequested.add(Manifest.permission.BLUETOOTH)

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED)
        missingPermissionsToBeRequested.add(Manifest.permission.BLUETOOTH_ADMIN)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        // For Android 12 and above require both BLUETOOTH_CONNECT and BLUETOOTH_SCAN
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)
            missingPermissionsToBeRequested.add(Manifest.permission.BLUETOOTH_CONNECT)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
            missingPermissionsToBeRequested.add(Manifest.permission.BLUETOOTH_SCAN)
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // FINE_LOCATION is needed for Android 10 and above
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
            missingPermissionsToBeRequested.add(Manifest.permission.ACCESS_FINE_LOCATION)
    } else {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
            missingPermissionsToBeRequested.add(Manifest.permission.ACCESS_COARSE_LOCATION)
    }

    if (missingPermissionsToBeRequested.isNotEmpty()) {
        writeToLog("Missing the following permissions: $missingPermissionsToBeRequested")
        ActivityCompat.requestPermissions(this, missingPermissionsToBeRequested.toArray(arrayOfNulls<String>(0)), REQUEST_MULTIPLE_PERMISSIONS)
    } else {
        writeToLog("All required permissions GRANTED !")
    }

}

然后onRequestPermissionsResult()可以检查用户是否批准了所需的权限

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_MULTIPLE_PERMISSIONS) {
        for (idx in permissions.indices) {
            var result = if (grantResults[idx] == PackageManager.PERMISSION_GRANTED) "Granted" else "Denied"
            writeToLog("REQUEST_MULTIPLE_PERMISSIONS Permission ${permissions[idx]} $result}")
        }
    }
}

Android在允许访问某些BLE功能之前需要进行权限检查。如果不授予权限,则仍必须进行检查,但可以通过添加Android版本检查来使其工作。
举个例子

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
    writeToLog("Scan permission denied")
} else {
    bluetoothScanner.startScan(filters, scanSettings, scanCallback)
    writeToLog("Bluetooth Scan Started")
}

最近@Kozmotronik改进了他的答案,如下所示

but5z9lq

but5z9lq1#

Google通过添加新的BLUETOOTH_CONNECTBLUETOOTH_SCAN权限使Android变得更加严格。如果您尝试访问任何需要这些权限的BLE API,您将在运行时获得SecurityException。因此我们需要检查清单文件中设置为android.intent.action.MAIN的Activity中的权限。我称之为MainActivity。不幸的是,我还没有使用Kotlin编写代码。所以我会用Java写例子。

主活动

public class MainActivity extends AppCompatActivity {

    private static int BLE_PERMISSIONS_REQUEST_CODE = 0x55; // Could be any other positive integer value
    private int permissionsCount;
    //...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other codes...
        checkBlePermissions();

        // Maybe some more codes...
    }

    // Maybe some other codes...

    private String getMissingLocationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // COARSE is needed for Android 6 to Android 10
            return Manifest.permission.ACCESS_COARSE_LOCATION;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // FINE is needed for Android 10 and above
            return Manifest.permission.ACCESS_FINE_LOCATION;
        }
        // No location permission is needed for Android 6 and below
        return null;
    }

    private boolean hasLocationPermission(String locPermission) {
        if(locPermission == null) return true; // An Android version that doesn't need a location permission
        return ContextCompat.checkSelfPermission(getApplicationContext(), locPermission) ==
                PackageManager.PERMISSION_GRANTED;
    }

    private String[] getMissingBlePermissions() {
        String[] missingPermissions = null;

        String locationPermission = getMissingLocationPermission();
        // For Android 12 and above
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if(ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN)
                    != PackageManager.PERMISSION_GRANTED) {
                missingPermissions = new String[1];
                missingPermissions[0] = Manifest.permission.BLUETOOTH_SCAN;
            }

            if(ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT)
                    != PackageManager.PERMISSION_GRANTED) {
                if (missingPermissions == null) {
                    missingPermissions = new String[1];
                    missingPermissions[0] = Manifest.permission.BLUETOOTH_CONNECT;
                } else {
                    missingPermissions = Arrays.copyOf(missingPermissions, missingPermissions.length + 1);
                    missingPermissions[missingPermissions.length-1] = Manifest.permission.BLUETOOTH_CONNECT;
                }
            }

        }
        else if(!hasLocationPermission(locationPermission)) {
            missingPermissions = new String[1];
            missingPermissions[0] = getMissingLocationPermission();
        }
        return missingPermissions;
    }

    private void checkBlePermissions() {
        String[] missingPermissions = getMissingBlePermissions();
        if(missingPermissions == null || missingPermissions.length == 0) {
            Log.i(TAG, "checkBlePermissions: Permissions is already granted");
            return;
        }

        for(String perm : missingPermissions)
            Log.d(TAG, "checkBlePermissions: missing permissions "+perm);
        permissionsCount = missingPermissions.length;

        requestPermissions(missingPermissions, BLE_PERMISSIONS_REQUEST_CODE);
    }

    // Maybe some other codes...

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode == BLE_PERMISSIONS_REQUEST_CODE) {
            int index = 0;
            for(int result: grantResults) {
                if(result == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Permission granted for "+permissions[index]);
                    if(permissionsCount > 0) permissionsCount--;
                    if(permissionsCount == 0) {
                        // All permissions have been granted from user.
                        // Here you can notify other parts of the app ie. using a custom callback or a viewmodel so on.
                    }
                } else {
                    Log.d(TAG, "Permission denied for "+permissions[index]);
                    // TODO handle user denial i.e. show an informing dialog
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

到目前为止,我们已经根据设备的SDK请求了所有需要的权限。现在我们需要阻止API调用路径以能够检查权限。在实现蓝牙扫描的某个地方放置一个函数,如下面的代码示例中的startScanning(),而不是直接使用BleScanner.scan() API。以下所有函数必须在同一个Activity或片段中。

执行扫描的其他Activity或片段

private String getMissingLocationPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
            && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        // COARSE is needed for Android 6 to Android 10
        return Manifest.permission.ACCESS_COARSE_LOCATION;
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // FINE is needed for Android 10 and above
        return Manifest.permission.ACCESS_FINE_LOCATION;
    }
    // No location permission is needed for Android 6 and below
    return null;
}

private boolean hasLocationPermission() {
    String missingLocationPermission = getMissingLocationPermission();
    if(missingLocationPermission == null) return true; // No permissions needed
    return ContextCompat.checkSelfPermission(requireContext(), missingLocationPermission) ==
            PackageManager.PERMISSION_GRANTED;
}

private boolean checkLocationService(@Nullable Runnable r) {
    boolean locationServiceState = isLocationServiceEnabled();
    String stateVerbose = locationServiceState ? "Location is on" : "Location is off";
    Log.d(TAG, stateVerbose);
    if(!locationServiceState){

        new MaterialAlertDialogBuilder(requireContext())
                .setCancelable(false)
                .setTitle("Location Service Off")
                .setView("Location service must be enabled in order to scan the bluetooth devices.")
                .setPositiveButton(android.R.string.ok, (dialog, which) ->
                        startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)))
                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
                    if(r != null) r.run();
                })
                .create().show();
    }
    return  locationServiceState;
}

private boolean isLocationServiceEnabled(){
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
        // This is provided as of API 28
        LocationManager lm = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE);
        return lm.isLocationServiceEnabled();
    } else {
        // This is deprecated as of API 28
        int mod = Settings.Secure.getInt(requireContext().getContentResolver(), Settings.Secure.LOCATION_MODE,
                Settings.Secure.LOCATION_MODE_OFF);
        return (mod != Settings.Secure.LOCATION_MODE_OFF);
    }
}

private void startScanning() {
    // Here we intervene the scanning process and check whether the user allowed us to use location.
    if(!hasLocationPermission()) {
        // Here you have to request the approprite location permission similar to that main activity class
        return;
    }
    // Location service must be enabled
    if(!checkLocationService(() -> // Pass a Runnable that starts scanning)) return;

    // Everything is good, CAN START SCANNING
}

这种逻辑本质上有点混乱,但它是健壮的,并且已经在Google Play商店中呈现的real application中运行。然而,它不是100%完整的代码,因为您需要将其背后的想法适应您的应用程序。

tyg4sfes

tyg4sfes2#

在我之前的回答中,我展示了如何使用旧的requestPermissions来请求运行时权限,并最终使用onRequestPermissionsResult回调来处理所请求的权限。
在这个答案中,我将基于前面的代码示例,展示如何使用名为ActivityResultLauncher的较新API来处理请求相同权限的问题。
作为奖励,我还添加了一个示例,显示如何请求用户启用BLE硬件并使用相同的API处理结果。
因此,最终您将能够使用ActivityResultLauncher API请求所需的运行时BLE权限和BLE硬件激活。

进入代码之前请注意,代码的位置可能会因应用程序而异,具体取决于其结构。这就是为什么开发人员有责任将代码仔细放置在适当的源文件中,以使其无问题运行。

public class MainActivity extends AppCompatActivity {

    private int permissionsCount;
    
    // ActivityResultLauncher for Bluetooth enable request process
    private final ActivityResultLauncher<Intent> bleEnableLauncher
            = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if(Activity.RESULT_OK == result.getResultCode()) {
                    Log.d(TAG, "User accepted to enable bluetooth");
                    // TODO Trigger a pending scanning or connection
                }
                else if(Activity.RESULT_CANCELED == result.getResultCode()) {
                    Log.d(TAG, "User rejected to enable bluetooth");
                    // TODO inform the user that the functionality of the app
                    // is reduced unless he/she decides to accept to enable it.
                }
            });
            
    // ActivityResultLauncher to handle multiple BLE permissions
    private final ActivityResultLauncher<String[]> permissionsResultLauncher = registerForActivityResult(
            new ActivityResultContracts.RequestMultiplePermissions(),
            permissions -> {
                for(Map.Entry<String, Boolean> permission: permissions.entrySet()) {
                    if(permission.getValue()) {
                        Log.d(TAG, "Permission granted for "+permission.getKey());
                        if(permissionsCount > 0) permissionsCount--;
                        else {
                            // TODO
                            // All permissions have been granted from user.
                            // Here you can notify other parts of the app ie. using a custom callback or a viewmodel so on.
                        }
                    } else {
                        Log.d(TAG, "Permission denied for "+permission.getKey());
                        // TODO handle user denial i.e. show an informing dialog
                    }
                }
            }
    );
    //...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other codes...
        
        // You can alternatively place these check functions in the onResume()
        // callback or before you access the BLE APIs
        checkBleEnabled(); // First check whether bluetooth is enabled
        checkBlePermissions(); // Then check whether the permissions are granted

        // Maybe some more codes...
    }

    // Maybe some other codes...
    
    private void checkBleEnabled() {
        BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
        if (bluetoothAdapter == null) {
            // TODO Handle -> Device doesn't support Bluetooth, probably
            // you'll want to finish the activity or inform the user then
            // return from here
        }
        if (!bluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            bleEnableLauncher.launch(enableBtIntent);
        }
    }

    private String getMissingLocationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // COARSE is needed for Android 6 to Android 10
            return Manifest.permission.ACCESS_COARSE_LOCATION;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // FINE is needed for Android 10 and above
            return Manifest.permission.ACCESS_FINE_LOCATION;
        }
        // No location permission is needed for Android 6 and below
        return null;
    }

    private boolean hasLocationPermission(String locPermission) {
        if(locPermission == null) return true; // An Android version that doesn't need a location permission
        return ContextCompat.checkSelfPermission(getApplicationContext(), locPermission) ==
                PackageManager.PERMISSION_GRANTED;
    }

    private String[] getMissingBlePermissions() {
        String[] missingPermissions = null;

        String locationPermission = getMissingLocationPermission();
        // For Android 12 and above
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if(ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN)
                    != PackageManager.PERMISSION_GRANTED) {
                missingPermissions = new String[1];
                missingPermissions[0] = Manifest.permission.BLUETOOTH_SCAN;
            }

            if(ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT)
                    != PackageManager.PERMISSION_GRANTED) {
                if (missingPermissions == null) {
                    missingPermissions = new String[1];
                    missingPermissions[0] = Manifest.permission.BLUETOOTH_CONNECT;
                } else {
                    missingPermissions = Arrays.copyOf(missingPermissions, missingPermissions.length + 1);
                    missingPermissions[missingPermissions.length-1] = Manifest.permission.BLUETOOTH_CONNECT;
                }
            }

        }
        else if(!hasLocationPermission(locationPermission)) {
            missingPermissions = new String[1];
            missingPermissions[0] = getMissingLocationPermission();
        }
        return missingPermissions;
    }

    private void checkBlePermissions() {
        String[] missingPermissions = getMissingBlePermissions();
        if(missingPermissions == null || missingPermissions.length == 0) {
            Log.i(TAG, "checkBlePermissions: Permissions are already granted");
            return;
        }

        for(String perm : missingPermissions)
            Log.d(TAG, "checkBlePermissions: missing permissions "+perm);
        permissionsCount = missingPermissions.length;

        permissionsResultLauncher.launch(missingPermissions);
    }

    // Other codes...
}

如果您在将示例应用到项目中时遇到问题,请随时询问。

相关问题