Runtime Permissions: Best Practices and How to Gracefully Handle Permission Removal

What Are Runtime Permissions?

With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use. As you probably already know, such a change requires efforts on the part of the application developer, but what happens if you do not implement this new model? Will your application still run? What changes do you have to make to be sure your application will run smoothly on all the versions you support?

This blog will step you through a sample application that demonstrates implementing the new permissions model all while answering commonly asked questions about the new model and how it affects your application.

When to Implement the New Model

The Good

The big question for many is: will my existing applications still work on Marshmallow devices? The good news is the new model has backwards compatibility- i.e. it doesn't require full support until you choose to target version 23 in your application. If you are targeting version 22 or below, your application will request all permissions at install time just as it would on any device running an OS below Marshmallow.

The Bad

Just because we can fall back on the old model, it does not mean that we can just avoid the new model by never targeting Marshmallow. A user with a Marshmallow device will now have the ability to revoke dangerous permissions via the application settings (we will get into what a dangerous permission is later). This means that even though the user accepted all your permissions at install time, they can later decide to take some of those permissions away from you.

The Ugly

If you have chosen not to implement the new permissions model, the revocation of permissions can cause unwanted user experiences and in some cases application crashes. The OS attempts to handle these scenarios as gracefully as possibly but developers should not rely on the OS to handle this scenario perfectly. In my experience, most permission revocations will not cause an application crash, but will absolutely degrade user experience. However, the side-effects of permission revocation are specific to an application's implementation and I highly suggest at the very least running your application on a Marshmallow emulator and testing your application with permission revocation.

How To Implement - Best Practices

Backwards Compatibility

When implementing the new permissions model it is important to do so in such a way that your application supports Marshmallow and pre-Marshmallow devices. For example, devices running a pre-Marshmallow OS do not support asking for permissions at runtime, thus we need to be smart about when we do make these requests. In the example application, I use the method below to determine if the user's device is a Marshmallow device or not. If the device they are using is not a Marshmallow device, we know the permissions are granted at install time and we do not need to ask to use the feature.

Dangerous vs. Non-Dangerous Permissions

Just like the old permission model, the new permission model requires all permissions to be included in the applications manifest. This is required regardless of what OS you are targeting or running. If you forget to register the permission in your applications manifest, the application will not be able to ask for the runtime permission later.

Android defines some permissions as "dangerous" and some permissions as "normal." Both are required in your application's manifest but only dangerous permissions require a runtime request. Normal permissions are accepted at install time and cannot be revoked later. An example of a normal permission is android.permission.INTERNET. Dangerous permissions are grouped into categories that make it easier for the user to understand what they are allowing the application to do. If the user accepts one permission in a group/category they accept the entire group. The opposite is true as well, if the user denies one permission in a group, the entire group in denied. The example application demonstrates this with both FINE_LOCATION and COARSE_LOCATION of the group LOCATION. You will notice that once the user has granted permission for one, the application is automatically granted permission for the other without the need to ask. The table below lists all the current dangerous permissions and their respective groups.

How to Ask

To simplify requests for features that may need more than one dangerous permission, you may ask for multiple permissions at a given time. Below you will find a code snippet from the attached example application where I am requesting access to both the CAMERA and RECORD_AUDIO permissions. Such a request might be necessary if you are creating a custom movie recorder, for example. In some cases you might use this when prompting the user with a "warm welcome" where you ask for some permissions at app launch that support the basic functionalities of the application.

The method requestPermissions(String[] permissions, int requestCode); is a public method found inside of the Android Activity class. Note that in this scenario above both the audio and camera permissions are of different groups and will prompt the user with two different permission dialogs to accept or decline. This means that the user can choose to accept one but not the other. If these two permissions were of the same group, the user would only see one permission prompt. You will receive the results of your request in the method onRequestPermissionResult as shown below.

After receiving the results, you will need to handle them appropriately. In the case of the request above, the feature about to be used needs both the camera and audio permissions. If the user denied either of the permissions, the feature will be unusable and an appropriate message should be displayed.

Don't Be Annoying

Once you have made a permission request to the user, you should not do so again unless the user requests you to do so. In the attached application I use the below method to determine if a specific permission has already been granted.

You should use the above method each time your application is about enter a feature that requires the use of a dangerous permission. Even if the permission has been previously granted it is necessary to check again to be sure that the user did not later revoke that permission. The above method leverages the method checkSelfPermission(String perm). This method is located in the Activity class and is used to return an integer value of PERMISSION_GRANTED or PERMISSION_DENIED.

In the event that you have already requested permission to access a specific feature and the permission was either denied or later revoked, you should not prompt the user again. Rather, you should notify the user in some way that you need access to a specific permission in order to continue. In the example application, I use a snackbar to let the user know that the permission was previously denied and should be granted to move forward. From the snackbar, I allow the user to request to be asked again. Remember to try and keep permission prompts to a minimum. If you do choose to allow the user to be prompted more than once, the second time you prompt the user for that same permission they should have the ability to choose not to be asked again in the future. If the user chooses to not be asked again, the application will not have the ability to ask the user for that permission again.

Unfortunately, there is nothing out of the box with the new permissions model that stores whether you have previously asked the user for permission or not. You will need to do so on your own by leveraging the SharedPreferences. In the attached example application, I use the methods below to check whether I have previously prompted the user for a permission or not and to record when I do so.

Don't Double Check

At times, just like the example we have been working with, multiple permissions are needed in order to access one feature. In the example of a movie recorder, both the camera and audio permissions are necessary. However, if the user has previously accepted either the camera or the audio permission, we should only prompt the user for the remaining permission. In the attached example application I use the below method to filter out the permissions already requested in the past to be sure we do not ask the user about a permission they have previously been prompted for. This does not mean that the permission was granted but rather that the permission was previously requested. Again, determining how many times to prompt a user for a permission they have previously denied is specific to your application but it is never necessary to prompt the user for a permission you already have access to.

Suggested Permissions Flow

The below diagram visually represents the implementation described above and in the attached example application. Note that this is a suggestion based on the Google guideline to keep repeated permission prompts minimal but your implementation may require a slightly different approach.

Closing

The new permissions model is definitely a step in the right direction for both the user and the developer. Users will soon have a better understanding of exactly why you need access to their contacts, which means less Play Store complaints and hopefully a higher download rate. The example application referred to in this blog can be found here. I give you permission to download and use the code!