One of the most talked about features of Android 6.0 (Marshmallow) was the finer permission controls, known as ‘Runtime Permissions’. Runtime Permissions allow the user to control whether a certain app has access to confidential data such as user’s contacts. Users can also take back permissions that they’ve already granted.
Before Marshmallow, all permissions were automatically granted during install. If the user did not agree with a certain permission, they would have to stop using the app altogether. In Android 6.0, system permissions are divided into 2 categories: normal and dangerous.
Normal permissions pose no direct risk to the users’ privacy or confidential data. Examples of normal permissions are
WAKE_LOCK, etc. When these permissions are declared in the app manifest, they’re automatically granted during install.
Dangerous permission protect the user’s privacy and confidential data. These permissions are not granted during install. Instead, the app has to make an explicit request at runtime, which is either accepted or declined by the user. Examples of dangerous permissions are:
The new permission model poses a few challenges to existing apps, some of which aren’t trivial. The challenges posed are: Changes in flow of control, external permission changes, and backwards compatibility.
Flow of Control
When the permission is automatically granted, the app can make a reasonable assumption that the permission will remain granted (exceptional cases could occur on rooted phones, but we won’t worry about them here). For example, let’s consider an Activity that displays the user’s contacts for the purpose of sending them an invite. Before Marshmallow, once the activity started, it could immediately start reading the contacts from the Contact provider. With the runtime permissions, this is no longer the case. What used to a be a sequential flow of control can now execute asynchronously. Linearity is preserved when the permission is currently granted, but by default the permissions are not granted. What previously executed immediately in your
onCreate method now has to wait until you’ve secured the required permission(s).
External Permission Changes
Aside from being able to grant or deny permissions at runtime, users can also open the app details page and remove a previously granted permission (or grant a previously removed permission). This means that the user can pull the carpet from under you by pausing the app, changing permissions, and returning to the app. Your app needs to be able to handle this and similar scenarios.
New features add methods and classes that aren’t available in previous versions of the platform. In order to make sure you don’t come across any
NoSuchMethodExceptions, you need to use the Compatibility classes available in the
support-v4 compatibility library:
- ActivityCompat (and FragmentCompat from
support-v13if you’re using native fragments) to request permissions and check if the rationale dialog should be displayed.
- ContextCompat to check permissions.
- OnRequestPermissionsResultCallback to receive permission request results
support-v4Fragment class already supports the proper methods.
Start by checking the current permission state for each permission that you need. Try to request each permission only when you need it so that you don’t scare the users (they’re frail beings). If the permission is already granted, continue on with accessing the data. If the permission isn’t currently granted there are a few possible scenarios:
- This is the first time the user is being asked for permission.
- The user has previously declined permission
- The user has previously declined permission and selected the ‘Never ask again’ checkbox.
Depending on which one of the above scenarios applies, you may be allowed to display a permission rationale dialog. The purpose of this dialog is to let the user know why you need these permissions. This is your chance to communicate to the user how their confidential data will be used. Scenarions 1 and 3 above will tell you that you shouldn’t display the rationale dialog because either the user has not yet denied you the permission OR the user has already made it clear that they don’t want to be asked again about this permission. You don’t need to keep track of these scenarios. Just call
shouldShowRequestPermissionRationale and display the rationale dialog if it returns true otherwise just continue to requesting permission.
At this point if the user has never been presented with the permission dialog or they’ve never selected the ‘Never ask again’ checkbox, they’re presented with a permission request dialog. Once the user clicks ‘Allow’ or ‘Deny’, the system calls your
onRequestPermissionsResult method with the request result(s). If the user has previously selected the ‘Never ask again’ checkbox, the permission request dialog is not displayed. Instead your
onRequestPermissionsResult method is called immediately indicating that your permission request was denied.
The demo code can be found on github. Checkout the
old_perms_model branch to see the original pre-Marshmallow code. The
master branch (at HEAD) contains the same functionality, but updated for runtime permissions. The two branches are set up to use a different application ID and name so that they can be installed on the same device and easily distinguished.
Read more about runtime permissions on Android Developers reference.