Error from lintReportDebug: Error: Call requires permission which may be rejected by user:


I copy-pasted some code from the Find BLE devices documentation, but I am getting this error:

/home/heller/DeliveryBox/MobileApp/DeliveryBox_Android/app/src/main/java/com/deepsoft/deliverybox/BluetoothManager.kt:55: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]
                                bluetoothLeScanner.stopScan(leScanCallback)
                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/heller/DeliveryBox/MobileApp/DeliveryBox_Android/app/src/main/java/com/deepsoft/deliverybox/BluetoothManager.kt:69: Error: Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with checkPermission) or explicitly handle a potential SecurityException [MissingPermission]
                bluetoothLeScanner.stopScan(leScanCallback)
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Here is the code file (BluetoothManager.kt):

/****************************************************************************
 *
 * Copyright (C) 2026 SEE FILE LICENSE.
 *
 * Synopsis:    Bluetooth Manager code
 * Authors:     Robert Heller
 *
 ****************************************************************************/

package com.deepsoft.deliverybox

import java.util.UUID
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult

import android.content.Context
import android.content.IntentFilter
import android.os.Build
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import android.os.Handler
import android.os.ParcelUuid

class BluetoothConnectionManager(private val context: Context) {
    private val ServiceUUID = UUID.fromString("3a6089ff-731f-4312-9761-6ecfa14b867e")
    private val WIFICharacteristicUUID = UUID.fromString("00e19a26-da6e-4ad2-896c-b5f0cbe04e43")
    private val MasterCodeCharacteristicUUID = UUID.fromString("fb90b3be-e21c-4aca-ae29-c05a0c3992e3")
    private val OneTimeCodeCharacteristicUUID = UUID.fromString("3d32154a-50f1-4e7d-883e-2b95ad680d3f")
    private val RestartCharacteristicUUID = UUID.fromString("2f1c6d70-9d97-47d7-8c9b-cb0b96bddea6")
    
    
    
    private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
    private var scanning = false
    private val bluetoothLeScanner = bluetoothAdapter?.getBluetoothLeScanner ()
    private val handler = Handler()
    
    // Stops scanning after 10 seconds.
    private val SCAN_PERIOD: Long = 10000
    
    private fun scanLeDevice() {
        if (!isBluetoothSupported()) return
        if (bluetoothLeScanner == null) {
            return
        }
        if (!scanning) { // Stops scanning after a pre-defined scan period.
            handler.postDelayed({
                                scanning = false
                                bluetoothLeScanner.stopScan(leScanCallback)
                            }, SCAN_PERIOD)
                scanning = true
                bluetoothLeScanner.startScan(listOf(ScanFilter.Builder()
                                                    .setServiceData(ParcelUuid(ServiceUUID), 
                                                                    null, 
                                                                    null).build()),
                                                    ScanSettings.Builder()
                                                    .setNumOfMatches(ScanSettings.
                                                                     MATCH_NUM_ONE_ADVERTISEMENT)
                                                    .build(),
                                                    leScanCallback)
            } else {
                scanning = false
                bluetoothLeScanner.stopScan(leScanCallback)
            }
        }
        
    // Device scan callback.
    private val leScanCallback: ScanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)
            //
        }
    }
    fun isBluetoothSupported(): Boolean = bluetoothAdapter != null
    
    fun isBluetoothEnabled(): Boolean = bluetoothAdapter?.isEnabled == true
    
    fun initialize() {
        if (isBluetoothSupported() && isBluetoothEnabled())
        {
            scanLeDevice()
        }
    }
    fun writeWiFiCharacteristic(ssid: String, password: String)
    {
    }
    fun writeMasterCodeCharacteristic(masterCode: String)
    {
    }
    fun writeOneTimeCodeCharacteristic(oneTimeCode: String)
    {
    }
    fun writeRebootCharacteristic()
    {
    }
}

sealed class ConnectionState {
    object Disconnected : ConnectionState()
    object Connecting : ConnectionState()
    data class Connected(val device: BluetoothDevice) : ConnectionState()
    data class Error(val message: String) : ConnectionState()
}

Note: I do have permission checking in the MainActivity.kt file.

Do I have to repeat the permission checks in the helper class? Is the Lint check really that stupid? Or is it that I need ALL of the code in one file or in one class?

I have added the full code on GitHub here.

0
Jul 25 at 11:02 PM
User AvatarRobert Heller
#android#kotlin#bluetooth-lowenergy

Accepted Answer

OK, I wrapped the whole if (!scanning) { ... } else {...} block in a try {...} catch, and lint is now happy. I don't think there will ever by an exception because I have permission checking in the outer code.

User AvatarRobert Heller
Jul 25 at 11:38 PM
1