Handling Android Runtime Permissions without using delays

168 views Asked by At

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
}
2

There are 2 answers

0
Etienne Lawlor On

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))
2
Rachit Garg On

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