0

I looked at the way that most devs handle Android runtime permissions and immediately after you are verifying that a permission is granted you are calling the API which requires that granted permission. Well I tried to do that but I got an exception thrown

Fatal Exception: java.lang.SecurityException Need android.permission.BLUETOOTH_CONNECT permission for android.content.AttributionSource@d1109f50: AdapterService getBondedDevices android.os.Parcel.createExceptionOrNull (Parcel.java:2426)

android.bluetooth.BluetoothAdapter.getBondedDevices (BluetoothAdapter.java:2491)
com.starmicronics.stario.a.b (a.java:19) 
com.starmicronics.stario.StarIOPort.searchPrinter (StarIOPort.java:36) 
com.dd.ddmerchant.printer.StarPrinterManager.init (StarPrinterManager.kt:72) 
com.dd.ddmerchant.printer.StarPrinterManager.connect (StarPrinterManager.kt:43) 
com.dd.ddmerchant.mainactivity.presentation.MainActivity$onRequestPermissionsResult$1.invoke (MainActivity.kt:701) 
com.dd.ddmerchant.mainactivity.presentation.MainActivity$onRequestPermissionsResult$1.invoke (MainActivity.kt:698) 
com.dd.ddmerchant.mainactivity.presentation.MainActivity$delay$$inlined$postDelayed$1.run (View.kt:412) android.os.Handler.handleCallback (Handler.java:938)

com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003)

so I tried to add a delay before interacting with the API that requires the granted permission, however on some devices that delay isn't long enough and I still get this error above on certain devices (although the delay seems to mitigate the issue for most devices) which may have other things running which causes a delay. How do you recommend handling this situation?

I just tried this approach but the code gets complicated and it doesn't work for every situation.

Here is my Permission code

object Permissions {

    @JvmStatic
    fun checkRuntimePermission(
        context: Activity,
        permission: String,
        isRationaleDialogHidden: Boolean,
        showRationaleDialog: () -> Unit,
        requestPermissionBlock: () -> Unit,
        permissionGrantedBlock: () -> Unit
    ) {
        when (checkSelfPermission(context, permission)) {
            PackageManager.PERMISSION_DENIED -> {
                if (ActivityCompat.shouldShowRequestPermissionRationale(context, permission)) {
                    if (isRationaleDialogHidden) {
                        showRationaleDialog()
                    }
                } else {
                    requestPermissionBlock()
                }
            }
            PackageManager.PERMISSION_GRANTED -> {
                permissionGrantedBlock()
            }
        }
    }

    @JvmStatic
    fun checkRuntimePermission(
        context: Activity,
        permission: String,
        permissionDeniedBlock: () -> Unit,
        permissionGrantedBlock: () -> Unit
    ) {
        when (checkSelfPermission(context, permission)) {
            PackageManager.PERMISSION_DENIED -> {
                permissionDeniedBlock()
            }
            PackageManager.PERMISSION_GRANTED -> {
                permissionGrantedBlock()
            }
        }
    }

    @JvmStatic
    fun isPermissionGranted(grantResults: IntArray): Boolean {
        var isPermissionGranted = true
        grantResults.forEach {
            if (it == PackageManager.PERMISSION_DENIED) {
                isPermissionGranted = false
            }
        }
        return isPermissionGranted
    }

    @JvmStatic
    fun isFineLocationPermissionGranted(context: Context): Boolean {
        return checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED
    }

    private fun checkSelfPermission(context: Context, permission: String) =
        ActivityCompat.checkSelfPermission(context, permission)
}

Here is how I request the permission

val requestBluetoothConnectPermissionBlock: () -> Unit = {
    ActivityCompat.requestPermissions(
        this@DeviceListActivity,
        arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
        BLUETOOTH_CONNECT_REQUEST_CODE
    )
}
checkRuntimePermission(
    context = this,
    permission = Manifest.permission.BLUETOOTH_CONNECT,
    isRationaleDialogHidden = isRationaleDialogHidden(),
    showRationaleDialog = { 
        showRationaleDialog(requestBluetoothConnectPermissionBlock) 
    },
    requestPermissionBlock = requestBluetoothConnectPermissionBlock,
    permissionGrantedBlock = { 
        bluetoothConnectPermissionGranted = true 
    }
)

And here is the override of onRequestPermissionsResult()

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
        BLUETOOTH_DISCOVERY_REQUEST_CODE -> {
            if (isPermissionsAllowed(grantResults)) {
                bluetoothDiscoveryPermissionGranted = true
            } else {
                Toast.makeText(
                    this,
                    getString(
                      R.string.cannot_set_up_printer_without_bluetooth_permissions
                    ),
                    Toast.LENGTH_LONG
                ).show()
                finish()
                return
            }
        }
        BLUETOOTH_CONNECT_REQUEST_CODE -> {
            if (isPermissionsAllowed(grantResults)) {
                bluetoothConnectPermissionGranted = true
            } else {
                Toast.makeText(
                    this,
                    getString(
                      R.string.cannot_set_up_printer_without_bluetooth_permissions
                    ),
                    Toast.LENGTH_LONG
                ).show()
                finish()
                return
            }
        }
    }
}

private fun isPermissionsAllowed(grantResults: IntArray): Boolean {
    var isPermissionsAllowed = true
    grantResults.forEach {
        if (it == PackageManager.PERMISSION_DENIED) {
            isPermissionsAllowed = false
        }
    }
    return isPermissionsAllowed
}
Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
  • Does [this](https://stackoverflow.com/a/74832806/12749998) or [this](https://stackoverflow.com/questions/72825519/android-permissions-check-for-ble/72842249#72842249) answer to your question? – Kozmotronik Jan 14 '23 at 09:11
  • @Kozmotronik the first link is talking about making sure bluetooth is turned on which is not my problem, the second link talks about requesting bluetooth low energy which is not exactly what i am trying to do. I am hoping for a straightforward implementation or some guidance in terms of what is wrong with my code i provided. – Etienne Lawlor Jan 14 '23 at 19:09
  • *the second link talks about requesting bluetooth low energy* - what does this stand for? There is no such thing -*requesting bluetooth low energy*-. You didn't specifed what type of bluetooth you use as peripheral device. Is it a BLE or classic? – Kozmotronik Jan 14 '23 at 19:23
  • 1
    Well the first link was for emphesizing the bluetooth API should be used in asynchronous manner; no delays, no busy waits etc. It is fully event driven. The second on was for handling all kind of runtime permissions for all API levels (up to 32). It is one of the best straightforward implementation of requesting runtime permission for BLE you can find. I will update it soon with the new `ActivityResultLauncher` API which is recommended by Google instead of the old `onRequestPermissionsResult`. – Kozmotronik Jan 14 '23 at 19:32

2 Answers2

0

I fixed my issues with the following changes :

  • Removed all the delay logic
  • Requesting runtime permissions in onStart() and not onResume() of the Activity
  • Updated logic in isPermissionGranted() to the following
@JvmStatic
fun isPermissionGranted(grantResults: IntArray) =
    ((grantResults.isNotEmpty() && 
    grantResults[0] == PackageManager.PERMISSION_GRANTED))
Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
0

I think you should remove the object keyword and use a class instead

  • What is your rationale here? I won't need to instantiate the class for anything. – Etienne Lawlor Jan 16 '23 at 06:37
  • object keyword is used to have a single instance throughout your app and you are checking the permission for the bluetooth and if that is not granted you are using the same instance of permission to request permission i thought that might cause error – Rachit Garg Jan 16 '23 at 06:45