Dealing with initial permissions requests in Android

David Santiago Turiño
Making Tuenti
Published in
6 min readApr 29, 2021

The importance of context to users

Credit: https://flic.kr/p/yALRk

Correctly managing runtime permissions is a subtle task for any app that requires them for its features to work as expected. Also, Android has evolved the way permissions are requested to users for the last years, so they are gradually more in control of what data and when is accessed by apps.

The importance of the context

When you ask a user of your app for a given permission it is important to follow Android recommendations and let her have as much context as possible for the permission request to succeed. For instance, if I want to change my profile picture and the app shows a button to do so, I won’t be surprised if right after that I see a system dialog asking for the camera permission. There is enough context to accept this request. Nevertheless, explicitly informing the user about the need for a permission that is about to be requested by the app is a good practice, as in these examples:

Telegram explaining phone permissions request
Google Fit describing why it needs location permission

What is more difficult is to ask for permissions required for certain features to work right after a user logs in your app, when there has been almost no time for her to interact with it and the context is therefore quite limited. They are what we internally call “initial permissions”.

We have been asking for several initial permissions across our Movistar, Vivo and O2 apps for quite some time already without providing the necessary context for such request so we decided to tackle this problem.

Features and initial permissions structure

The different features of our app are organized in a multi-module structure and some of them require initial permissions in order to work as expected.

Previously we already had a system in place for such features to specify what permissions they require and let the permissions module collect them and ask the user for those not granted and still not requested. This was simple enough to perform the required permissions requests without those features having to deal with it. Feature modules just had to declare a Dagger provider with the set of permissions required and use a custom @InitialPermission annotation for it.

But as I mentioned before, Android recommends providing context for this kind of requests (in fact, Android has changed in the last releases of the OS from recommendation to obligation when it comes to providing context before asking for the background location permission, for instance), with a trend towards a more restrictive environment to further protect users’ privacy. Just asking for one or more permissions without a previous explanation was not the way to go, as our initial permissions denial percentages indicate (up to 60% in the worst scenario).

Extended permissions configuration for features

Collaborating with Product and Design we agreed on showing an explanatory screen to users before requesting initial permissions, with the possibility of navigating to a second one with further details if desired.

Other alternatives were discarded, like showing a explanatory screen per feature or permission request. Why? Mostly because we wanted this process to be as expedite as possible, considering that the user might also come from seeing previous screens from our onboarding system and we know how reticent users are to read content from successive screens.

Also we focused the rationale on the features that require permissions rather than the permissions themselves, as in the end the features are what users are most interested in. Thereby, every feature has texts explaining what the user will benefit from when requested permissions are accepted.

For consistency we took advantage of the extensibility of the in-app messages feature used in the onboarding to evolve it to be able to present the rationale for initial permissions request with the same design, instead of crafting a new, custom one.

For this to work we extended the existing model used by features to specify their initial permissions. We kept the existing annotation but switched from passing permissions as strings to a richer model, which includes the string id of the feature (used to distinguish what permissions have already been asked per feature), the actual set of permissions required, and the @StringRes ints for the texts that will be used to explain why a feature requires certain permissions (some corresponding to brief explanations in the explanatory screen and others for the detailed one).

Provider for a “contacts” feature that requires some initial permissions
Provider of another feature that needs different initial permissions

As you can see, configuring the initial permissions and their rationale for any new module that requires it is pretty simple.

With all this information received via dependency injection with Dagger the permissions module is able to determine what features require initial permissions, which ones have already been asked per feature, if any, so they are not requested again, and ultimately build the explanatory and detailed screens if needed. Users can then check the provided explanations and confirm that is ok for them to start the permissions requests that now happen with the context of the previous explanations in place.

One more thing

There is something else we would have liked to do but we were not able to with the current behavior of Android’s PackageManager that I will explain later.

Android permissions are organized in groups for the sake of simplifying permissions requests to users, minimizing their number. Asking for a not-yet-granted runtime permission results in the OS showing a dialog to the user for her to accept or deny the group that permission belongs to. Then, subsequent permissions requests performed by the app for permissions that belong to the same group than another previously granted will be automatically (and silently, without the user seeing more dialogs), granted to the app.

We wanted to take advantage of this in order to avoid the following scenario: a new feature is added to the app, which requires permission A that belongs to group X. Previously, the user already accepted a permission request for permission B that also belongs to group X. As this is a new feature with a permission not yet granted nor asked to the user, we show the rationale screen to the user with the information for such feature. Afterwards, the app requests the permission A but no dialog is shown to the user; Android silently grants and notifies the app that permission A is granted, as group X is already granted.

The thing is that asking Android’s PackageManager for the list of permission groups and the permissions inside them results in something like a permission group called android.permission-group.CONTACTS being returned without any permissions inside it, or permissions READ_CONTACTS or CALL_PHONE appearing under a android.permission-group.UNDEFINED, so we had no way of performing a check that would prevent users from seeing information about a new feature that requires a permission that is going to be granted by the system without another explicit approval on her side.

Nevertheless, Android documentation has several places where they discourage app developers from implementing logic based on this relationship, as in:

“Also, the text in the system permission dialog references the permission group associated with the permission that you requested. This permission grouping is designed for system ease-of-use, and your app shouldn’t rely on permissions being within or outside of a specific permission group.”

so it looks like we will have to stick to that after all.

--

--