Skip to content

# Requesting User Permissions

Nix devices communicate using Bluetooth LE which require the user to grant additional permissions. The required permissions depend on the Android version and were previously included under location services. Later versions of Android use specific Bluetooth permissions that are decoupled from location. The `IDeviceScanner` interface defines several library functions which streamline and simplify these requests across all supported Android versions.

Warning

Device discovery will be impossible if the required permissions are not granted by the user.

## Determining which permissions are required

To simplify permission checks, the `IDeviceScanner/getRequiredBluetoothPermissions()` helper can provide the list of required permissions at runtime (see Kotlin or Java APIs).

Tip

Check `getRequiredBluetoothPermissions()` after activating the SDK license to ensure accuracy.

Kotlin Java

    // Helper to provide list of required permissions for BLE at runtime
    // Call after LicenseManager.activate()
    var requiredPermissions = IDeviceScanner.requiredBluetoothPermissions

    // Helper to provide list of required permissions for BLE at runtime
    // Call after LicenseManager.shared.activate()
    String[] requiredPermissions =
        IDeviceScanner.Companion.getRequiredBluetoothPermissions();

The expected result for the required permissions includes:

- Android API level 30 (Android 11 'Red Velvet Cake' and older)
  - `ACCESS_COARSE_LOCATION`
  - `ACCESS_FINE_LOCATION`
- Android API level 31 (Android 13 'Snow Cone' and newer)
  - `BLUETOOTH_SCAN`
  - `BLUETOOTH_CONNECT`

Warning

Our testing has found that location permissions are still sometimes needed when running in a managed profile (such as an Android work profile). This is despite the `NixUniversalSDK` strongly asserting that it does not derive location from Bluetooth scan results. This behaviour appears to be an Android bug. For this reason, `getRequiredBluetoothPermissions()` currently includes location permissions as a workaround if it determines that it is running within a managed profile.

## Checking if permissions are granted

Prior to searching for any devices, use the `IDeviceScanner/isBluetoothPermissionGranted()` helper to check if the user has already granted the required permissions (see Kotlin or Java APIs).

Tip

Check `isBluetoothPermissionGranted()` after activating the SDK license to ensure accuracy.

Kotlin Java

    // Helper to determine if permissions are already granted
    // Call after LicenseManager.activate()
    if (IDeviceScanner.isBluetoothPermissionGranted(requireContext())) {
        // All permissions are granted, OK to start scanner!
        // ...
    } else {
        // Some permissions are missing, request them!
        // ...
    }

    // Helper to determine if permissions are already granted
    // Call after LicenseManager.activate()
    Context context = getContext();
    if (IDeviceScanner.Companion.isBluetoothPermissionGranted(context)) {
        // All permissions are granted, OK to start scanner!
        // ...
    } else {
        // Some permissions are missing, request them!
        // ...
    }

## Requesting missing permissions

If it is determined that permissions are missing, use an `ActivityResultLauncher` to request them from the user. Examples are provided below.

Tip

It is best practice to show a primer message to the user prior to launching the request. This message should explain why the permissions are being requested. If denied, you should warn the user that Nix device functions will be unavailable unless permissions are granted.

Kotlin Java

    /**
     * Callback for Bluetooth permissions request result
     */
    private val bluetoothPermissionRequestLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
            var allGranted = true
            permissions.forEach { allGranted = allGranted and it.value }
            if (allGranted) {
                // All permissions granted, OK to use `DeviceScanner`
                // ...
            } else {
                // Handle permission denial
                // ...
            }
        }

    /**
     * Launches a request to the user to grant necessary permissions to use
     * Bluetooth after displaying a primer message dialog.
     */
    private fun requestBluetoothPermission() {
        // Prepare an alert dialog to prime the user for the permission request
        // Customize the message as required
        val safeContext = context ?: return
        val builder = AlertDialog.Builder(safeContext)
        builder.setTitle("Required permissions")
            .setMessage(
                "Additional permissions are needed to find nearby Nix devices. " +
                "Please grant these at the following prompt")
            .setPositiveButton("OK"
            ) { _, _ ->
                // Launch the permission request
                bluetoothPermissionRequestLauncher.launch(
                    IDeviceScanner.requiredBluetoothPermissions)
            }
        builder.show()
    }

    /**
    * Callback for Bluetooth permissions request result
    */
    private final ActivityResultLauncher<String[]> mPermissionRequestLauncher =
        registerForActivityResult(
            new ActivityResultContracts.RequestMultiplePermissions(),
            permissions -> {
                boolean allGranted = true;
                for (boolean granted : permissions.values())
                    allGranted = allGranted & granted;
                if (allGranted) {
                    // All permissions granted, OK to use `DeviceScanner`
                    // ...
                } else {
                    // Handle permission denial
                    // ...
                }
        });

    /**
     * Launches a request to the user to grant necessary permissions to use
     * Bluetooth after displaying a primer message dialog.
     */
    private void requestBluetoothPermission() {
        // Prepare an alert dialog to prime the user for the permission request
        // Customize the message as required
        Context context = getContext();
        if (context == null) return;

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Required permissions")
            .setMessage(
                "Additional permissions are needed to find nearby Nix devices. " +
                "Please grant these at the following prompt")
            .setPositiveButton("OK", (
                dialogInterface, i
            ) -> {
                // Launch the permission request
                mPermissionRequestLauncher.launch(
                    IDeviceScanner.Companion.getRequiredBluetoothPermissions());
            });
    }

## Next steps

- Discovering and Connecting to Nix Devices