Lessons learned: Adding support for runtime permissions to Firefox for Android
Up until recently users granted permissions to apps when they installed or updated the apps. Beginning in Android 6.0 users now grant permissions to an app while the app is running — if supported by the app (Android documentation).
Firefox for Android 46.0 (Currently in the beta channel) is the first version that supports runtime permissions. This article is not a guide on how to add runtime permissions to your app (There are other good ones: Styling Android — Permissions for example). Instead it is a collection of observations and lessons learned while adding support to Firefox for Android.
You can get a lot of permissions without asking the user now
Android divides system permissions into two categories (protection level): normal and dangerous.
Normal permissions shouldn’t affect the user’s privacy and therefore are granted at install time. You still need to add the permissions to the manifest but you don’t need to prompt the user in order to use it. This also means that the user can’t revoke those permissions. An example for a normal permission is the NFC permission. On Android 6+ every app can have it (if listed in the manifest) and use it without any further interaction from the user needed.
Dangerous permissions on the other hand might affect the user’s privacy and need to be accepted first.
Updated protection levels
With Android 6 the protection levels of several permissions have been updated.
Most noteworthy changes:
- The INTERNET permission now has protection level “normal” — every Android app targeting SDK 23 can access the internet.
- READ_EXTERNAL_STORAGE is “dangerous” now. Apps will need to prompt for the permission at runtime.
Android categorizes all dangerous permissions into 9 groups. When requesting a specific permission you are actually asking for all permissions in the group. For example when requesting the READ_CONTACTS permission then the system will show a generic prompt for the CONTACTS group. After accepting the permission and once you ask for another permission from this group, say GET_ACCOUNTS, the system will automatically grant you the permission without prompting the user. You will still have to request permissions from the group — especially because those groups can change in the future — but in the worst case the user won’t see more than 9 different prompts currently.
At runtime you can get more permissions but never less
At runtime your app might already have some permissions and you can request additional permissions when needed. An app can’t dismiss a permission itself and the user has to go to Android’s settings first in order to revoke a permission. If permissions are revoked from an app then Android will kill the app. As a consequence your app can get more permissions but it can never have less while it is running.
This is great news for app developers: Usually it’s enough to guard the entry point to code requiring a permission and you do not need to wrap every code requiring a permission in a permission check.
For example if you are writing a background service that requires the dangerous READ_CONTACTS permission then it’s enough to check the permission at the start of the service and stop the service immediately if you do not have the permission. All other code in the service won’t need to perform the check because the permission can’t be revoked while it is running.
Official API and libraries
The official API for runtime permissions is a bit clunky. The reason for that is that it is heavily tied to the Activity class. This somehow makes sense: You prompt for a permission by showing a dialog and this is something you do with an Activity context. By now there are plenty of libraries for runtime permissions on GitHub.
You can find the code here.
Turning down permission requests
Of course a user can turn down permission requests. Android provides a utility method, shouldShowRequestPermissionRationale(), that returns true if the user has denied the request previously and you should show UI explaining why the app needs the permission.
A special case is the “Never ask again” checkbox that appears for subsequent requests. There’s no official API for checking whether the request has been denied permanently by the user. However you can guess it from the return values of shouldShowRequestPermissionRationale() after you have requested the permission:
A value of false after requesting a permission and the user denying it means that you do not have the permission and should not show a rationale the next time: The permission has been denied permanently.
For permanently rejected permissions you will not be able to show the prompt to the user again. As a last resort you can only send your users to the system’s settings to manually edit the permissions of the application.
UI testing with runtime permissions
Testing the UI of your application with Robotium or Espresso on Android 6+ can be a challenge if you are using runtime permissions. The frameworks are not able to interact with the permission dialogs.
Instead you can either grant specific permissions via adb or install the APK with all permission granted automatically:
After that the app will have all needed permissions and no prompts will be shown during test. To test the behavior of permissions not being granted you will have to mock your implementation of checking and requesting permissions to return values as if the user would have denied the request.
Updating the application to a version with targetSdkVersion 23
There’s good news for everyone with an application that doesn’t target SDK 23 yet. As soon as your users update to a new version targeting 23 the permissions that have already been granted at install time in the past will still be granted after the update. So if your app has the camera permission then after update you won’t need to prompt for the camera permission. Only new installations won’t have the permission until you ask for it.