From the beginning of Android 6.0 Marshmallow, Google has introduced a new runtime permission model, users are not asked for permissions at the time of installation rather developers need to request the permissions at the run time. Only the permissions that are defined in the manifest file can be requested at run time.

This helps in protecting the privacy of an Android Device user. So for example you(your apps) have to request permissions to:

  1. Access sensitive user data (such as contacts and SMS).
  2. Access certain system features (such as camera and internet).

Depending on the feature, the system might grant the permission automatically or might prompt the user to approve the request.

Type of permissions

Android defines basically three types of permissions:

  1. Normal Permissions
  2. Signature Permissions
  3. Dangerous Permissions

Both Normal and Dangerous permissions must be defined in the Manifest file. But only Dangerous permissions are checked at runtime, Normal permissions are not.

Normal Permissions

These are required when your app needs to access data or resources outside the app’s sandbox, but where there’s very little risk to the user’s privacy or the operation of other apps.

If an app declares in its manifest that it needs normal permission, the system automatically grants the app that permission at install time. and users cannot revoke these permissions.

Permissions like INTERNET, BLUETOOTH, ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE, etc are normal permissions.

For more details about the permission check the permission API reference page.

Signature Permissions

A permission that the system grants only if the requesting application is signed with the same certificate as the application that declared the permission. If the certificates match, the system automatically grants permission without notifying the user.

Permissions like BIND_AUTOFILL_SERVICE, BIND_INPUT_METHOD, BIND_NFC_SERVICE, etc are signature permissions.

Dangerous permissions

Some permissions that may affect the user’s private information, or could potentially affect his data or the operation of other applications are called Dangerous Permissions. For example, the ability to read the user’s contacts is a dangerous permission.

Permissions like CAMERA, READ_CALL_LOG, ACCESS_FINE_LOCATION, READ_SMS, READ_EXTERNAL_STORAGE, etc are dangerous permissions.

For more details about the permission check the permission API reference page.

How to Check for permissions

If your app needs dangerous permission, you must check whether you have that permission every time you perform an operation that requires that permission.

you do it using the ContextCompat.checkSelfPermission() method with the permission name.

if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            // permission granted
        } else {
            // permission not granted
        }

This will return two types of responses.

  • PackageManager.PERMISSION_GRANTED – This means permission is granted. So you can proceed with your operations.
  • PackageManager.PERMISSION_DENIED – This means permission is not granted. So you need to ask permission again or you can skip that process.

Request permissions

If permission is not granted, we need to ask for permission to access certain features like cameras, storage, etc.

To ask permission, we need to use ActivityCompat.requestPermission() method with the permission name and the request code.

First, we need to add the requested permission to the manifest.xml file.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Note: don’t forget to add all the needed permission into the manifest file. otherwise, run time permission won’t work.

ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.CAMERA),
                        request_code
                    )

This will pop up the permission dialog for the defined permission. Users can able to Allow or deny permission.

Camera Runtime permission

Handle the permission request response

Once the user responded to the permission request, the result will be received in the onRequestPermissionResult. based on the request code, we can check whether our permission was granted or denied.

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 201) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
                openCamera()
            } else {
                Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            }
        }
    }

Requesting multiple permissions

Previously, we checked about requesting single permission at a time. In Android, it’s also possible to ask for multiple permission at a time. We can use the same ActivityCompat.requestPermissions() the method as above by passing an array of permissions.

 ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE),
                        request_code
                    )

But, we cannot check the multiple permission at a time. We need to check the permission status individually.

Handle the permission denial

When the user is denied permission for the first time. It means that the user doesn’t understand the purpose of the particular permission. So, we need to show some additional messages to explain the purpose of the permission.

To do that we need to check for the shouldShowRequestPermissionRationale() method with the permission to check, whether the permission is already denied or not. If denied already need to show some additional message using the dialog.

if (checkPermission(Manifest.permission.CAMERA)) {
                openCamera()
            } else {
                if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                    AlertDialog.Builder(this)
                        .setMessage("Need camera permission to capture image. Please provide permission to access your camera.")
                        .setPositiveButton("OK") { dialogInterface, i ->
                            dialogInterface.dismiss()
                            ActivityCompat.requestPermissions(
                                this,
                                arrayOf(Manifest.permission.CAMERA),
                                201
                            )
                        }
                        .setNegativeButton("Cancel") { dialogInterface, i ->
                            dialogInterface.dismiss()
                        }
                        .create()
                        .show();
                } else {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.CAMERA,Manifest.permission.READ_EXTERNAL_STORAGE),
                        201
                    )
                }
            }
Additional Permission Message

One-time permissions

Starting in Android 11, whenever your app requests permission related to location, microphone, or camera, the user-facing permissions dialog contains an option called Only this time. If the user selects this option in the dialog, your app is granted temporary one-time permission.

Android one-time permission

checks more about one-time permission.

Android Runtime Permission Example

check out my final code for Runtime permission MainActivity.kt,

class MainActivity : AppCompatActivity() {

    lateinit var btnCamera: Button
    lateinit var btnLocation: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCamera = findViewById(R.id.btnCamera)
        btnLocation = findViewById(R.id.btnLocation)

        btnCamera.setOnClickListener {

            if (checkPermission(Manifest.permission.CAMERA)) {
                //openCamera()
            } else {
                if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                    AlertDialog.Builder(this)
                        .setMessage("Need camera permission to capture image. Please provide permission to access your camera.")
                        .setPositiveButton("OK") { dialogInterface, i ->
                            dialogInterface.dismiss()
                            ActivityCompat.requestPermissions(
                                this,
                                arrayOf(Manifest.permission.CAMERA),
                                201
                            )
                        }
                        .setNegativeButton("Cancel") { dialogInterface, i ->
                            dialogInterface.dismiss()
                        }
                        .create()
                        .show();
                } else {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.CAMERA,Manifest.permission.READ_EXTERNAL_STORAGE),
                        201
                    )
                }
            }


            /**/

        }
        
    }

    private fun checkPermission(permission: String): Boolean {
        return ContextCompat.checkSelfPermission(
            this,
            permission
        ) == PackageManager.PERMISSION_GRANTED
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 201) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
                //openCamera()
            } else {
                Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            }
        }
    }

}

also, updated Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.velmurugan.runtimepermissionandroid">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.RuntimePermissionAndroid">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Thanks for reading. let me know your feedback on the comments. Also, you can download this example on GITHUB.

One Reply to “Android Runtime Permissions[Updated with Android 11]”

Leave a Reply

Your email address will not be published.