Android Permission Model

This tutorial is about Android runtime permission model that changed in Android Marshmallow release.

Google Play will require that new apps target at least Android 8.0 (API level 26) from August 1, 2018, and that app updates target Android 8.0 from November 1, 2018.

Google’s this decision led developers to change development priorities for their applications. Now a huge number of apps need to be updated, and they should also need to change the permission model. This tutorial guides developer on how to change current permission model into the new one.

New Runtime Permissions vs. Old Model

Android application permission handling has changed to another dimension with API level 23. In the old model all permissions were asked to be accepted during installation. But now, after Marshmallow non-normal permissions are asked to be granted during runtime.

Permissions are divided into four protection levels :

  1. Normal
  2. Signature
  3. Dangerous
  4. Special

We will look through normal and dangerous levels. If you are interested, you can jump to Bonus Section for Special and Signature levels. Here are the differences between normal and dangerous permissions:

Normal permissions have nearly no risk to user privacy or to the operation of other applications. For example, permission for connection to the Internet or setting the alarms.

These type of permissions are automatically granted by the system during installation, and the user will not be prompted explicitly to grant the permission. As well users cannot revoke these permissions.

See the complete list of normal permissions on Android’s website.

Dangerous permissions can cause data leak that involves the user’s private information, change the user’s saved data or risk the operation of other applications. For example, permission for the opening camera and taking pictures, sending sms or recording through microphone.

To use a dangerous permission, the user should grant the permission via prompt shown at runtime or from system settings. The user can also revoke this type of permissions at any time.

See the complete list of dangerous permissions on Android’s website.

Requesting Android Runtime Permissions

For implementing permission model you will need these four functions :

  • ContextCompat.checkSelfPermission() to check the permission is granted.
  • ActivityCompat.requestPermissions() to request the permissions. Multiple permissions can be requested with multiple permission groups.
  • ActivityCompat.shouldShowRequestPermissionRationale() to decide to show rationale message about why the permission wanted to be granted.
  • onRequestPermissionsResult() to handle the user’s response to permission prompt.

Let’s say you want to manage call logs in your app. The permissions required are in dangerous level so first, you need to check the required permission if it is granted or not.

    private val REQUEST_CODE = 1234
    ...
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALL_LOG) == PackageManager.PERMISSION_GRANTED) {
        // Run your logic ideally with pre-granted permission.
    } else {
        // Here you do not have the permission. So you need to request it.
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG), REQUEST_CODE)
    }
    
    private static final int REQUEST_CODE = 1234;
    ...
    if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALL_LOG) == PackageManager.PERMISSION_GRANTED) {
        // Run your logic ideally with pre-granted permission.
    } else {
        // Here you do not have the permission. So you need to request it.
        ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG }, REQUEST_CODE);
    }

The parameter of REQUEST_CODE is the identifier of our request action. It will help us to locate our action in onRequestPermissionsResult function.

In this stage, if the app is not pre-granted the permissions, the application should show a prompt to the user. Here in the code, two separate permissions are requested, but only one prompt with group-generic “make and manage phone calls” text is shown. The reason behind it is that Google has grouped dangerous permissions by in themselves.

See dangerous permission groups on Android’s website.

The grouping is helpful these ways :

  • If the app doesn’t currently have any permissions in the permission group, the system shows the permission request dialog to the user describing the permission group that the app wants access to.
  • If the app has already been granted another dangerous permission in the same permission group, the system immediately grants the permission without any interaction with the user.

Let’s request second time the same permissions. Here the additional “Don’t ask again” checkbox appears.

The user has three choices for the prompt :

  1. Allow – Allows the permission grant – the happy path.
  2. Deny – Denies the permission grant but the app can request it again.
  3. Don’t ask again – Denies the permission and block app to request the permission again.

On selecting any choice, prompt will disappear and onRequestPermissionsResult() will be called.

override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array<String>, @NonNull grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    when (requestCode) {
        REQUEST_CODE -> {

            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Run your logic with newly-granted permission.
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CALL_LOG)) {
                // Permission denied.
                // User selected "Don't ask again" choice.
                // Change the UI accordingly.
            } else {
                // Permission denied.
                // User selected "Deny".
                // App can re-request permission. Change your logic accordingly.
            }

            // Do not forget to control each permission.
        }
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {
        case REQUEST_CODE: {

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Run your logic with newly-granted permission.
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CALL_LOG)) {
                // Permission denied.
                // User selected "Don't ask again" choice.
                // Change the UI accordingly.
            } else {
                // Permission denied.
                // User selected "Deny".
                // App can re-request permission. Change your logic accordingly.
            }
            
            // Do not forget to control each permission.
        }
    }
}

In this block shouldShowRequestPermissionRationale() method helps us to determine the denial reason. If the user selected “Don’t ask again” choice, the application should explicitly help the user to understand why the permission is needed; i.e. some graphics, explanation texts or UI changes.

Here below there is the diagram of the whole process in one shot.


Bonus Section

We need to think of these Signature and Special permission groups together. Because special permissions are also considered as signature permissions by Google.

Signature permissions

Signature permissions are automatically granted to the application by the system during installation like normal permissions. However, the difference here is that not every application can get these permissions. If the application is signed by the same certificate which signs the permission, then the application can use the permission.

When you define a custom permission in one of your applications, and if you set its protection level to “signature”; only the applications, which is signed by the same certificate which used in permission-defined-application, can grant and use that permission, not any other applications.

Special permissions

Some of the signature permissions are above the dangerous permissions, which means can effect and change the user’s whole usage of the phone. These permissions are not used by most of the applications. But in rare cases, you need to use them. Your application can not grant these but asks the user to manually add the permissions to the app over system settings.

Here are two of the specials:

SYSTEM_ALERT_WINDOW

This permission allows the application to “draw over screen” like Facebook chat heads and Truecaller’s caller card over the screen. If your application needs this special permission, you can not use regular request flow. Here you should call with intent to open system permission management screen to the user for the grant.

    fun checkDrawOverlayPermission() {

        // Check if the app already has the permission
        if (Settings.canDrawOverlays(this)) {
            // Run your logic with pre-granted permission.
        } else {
            // Intent to request the permission
            val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))

            // Start the activity
            startActivityForResult(intent, REQUEST_CODE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {

        when (requestCode) {
            REQUEST_CODE -> {
                // Check if the app get the permission
                if (Settings.canDrawOverlays(this)) {
                    // Run your logic with newly-granted permission.
                } else {
                    // Permission not granted. Change your logic accordingly.
                    // App can re-request permission anytime.
                }
            }
        }
    }
    public void checkDrawOverlayPermission() {

        // Check if the app already has the permission
        if (Settings.canDrawOverlays(this)) {
            // Run your logic with pre-granted permission.
        } else {
            // Intent to request the permission
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));

            // Start the activity
            startActivityForResult(intent, REQUEST_CODE);

        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,  Intent data) {

        switch (requestCode) {
            case REQUEST_CODE: {

                // Check if the app get the permission
                if (Settings.canDrawOverlays(this)) {
                    // Run your logic with newly-granted permission.
                } else {
                    // Permission not granted. Change your logic accordingly.
                    // App can re-request permission anytime.
                }
            }
        }
    }

WRITE_SETTINGS

This permission allows the application to “change system settings” like admin applications, and Bluetooth manager applications. Handling is the same with other special permission. But you should ACTION_MANAGE_WRITE_SETTINGS and Settings.System.canWrite() instead ACTION_MANAGE_OVERLAY_PERMISSION and Settings.canDrawOverlays().

Happy coding 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *