Android M has officially been revealed to be Android Marshmallow, version 6.0. Among a number of improvements and changes are permissions. Previously, permissions were required to be accepted by the user before installing the app, and they could never be revoked. This meant that the only way to prevent an app from accessing something it requested was to not use the app. The new setup instead gives users granular control over which permissions are allowed on a per app basis.

The new permissions model has these key components:
  • Declaring Permissions
  • Permission Groups
  • Limited Permissions Granted at Install Time
  • User Grants Permissions at Run-Time

Some of these are very familiar to Android developers, but some of these are completely foreign. We'll cover each of them in detail.

 

Declaring Permissions

Permissions your app will need still need to be declared in the manifest, although you usually can not assume you have access to a permission.

Here is an example of how to declare a permission for interacting with the user's Contacts in your AndroidManifest.xml:

<uses-permission android:name="android.permission-group.CONTACTS" />

 

Permission Groups

You might have noticed the example XML code from the manifest above is requesting a permission group - permissions are now separated into groups based on functionality. For example, if your app needs to integrate with the user's SMS message, there is a permission group android.permission-group.SMS that will provide a list of permissions for interacting with SMS, including both the SEND_SMS and RECEIVE_SMS permissions.

While the old individual permissions are still available, you should make an effort to use these groups. It will improve the flow of your app by allowing a set of permissions at once instead of prompting the user multiple times for related functionality.

Let's say your app needed to both read and write to the user's calendar. Instead of prompting twice, once for read access and then once for write access, you can instead use the permission group CALENDAR to do both. If the first interaction your app has with the user's calendar is reading his or her events, then you can request for the permission group, which then displays a dialog box for the user explaining that the permission group was requested. If granted, the app can now read the user's calendar. (See below for best practices for gracefully handling denied permissions). Now if the user performs an action that requires the ability to write to the calendar, he or she will not receive a dialog box, because they've already granted access to the calendar permission group.

Here is a list of permission groups.

 

Limited Permissions Granted at Install Time

There are a number of permissions automatically granted to all apps that request them now. That is, they do not prompt the user directly for access and instead are immediately approved. You still need to request them in the manifest.

The list of permissions is based on those that are classified as PROTECTION_NORMAL. This list is curated to include only access to the phone that are not intrusive. For example, accessing Bluetooth is a simple interaction that you can use without approval, while accessing phone's GPS must be approved.

Here is a list of what currently falls under PROTECTION_NORMAL.

 

User Grants Permissions at Run-Time

There are two ways to perform functionality: intents and permissions

Intents

Intents simply delegate the action to the system. For example, if you simply need the user to capture an image using their camera, you can use the ACTION_IMAGE_CAPTURE intent to request an image and handle the result callback in the onActivityResult() method. This exists already in Android, so we will not be going into detail on it.

Permissions

Requesting and receiving permissions gives you much better control over the phone's capabilities. So if we needed an image as explained in the paragraph above, but wanted to use custom UI, we could request the camera permission and use it directly.

You should build your app around the idea that any permission that you request can be approved or denied; this includes the user denying it after having previously approved it (see below for best practices for gracefully handling denied permissions).

For this example, let's imagine you're building an app that interacts with external storage (both reading and writing). Android provides a few helpful methods to assist with this:

// Methods you call
public abstract int checkSelfPermission (String permission);
public final void requestPermissions (String[] permissions, int requestCode);
public boolean shouldShowRequestPermissionRationale (String permission);

// Callbacks
public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults);

In our example, the user has just tapped a button that required the ability to read external storage. Our first step is to check if the app already has the permission (Note that you request for specific permissions, not permission groups.):

if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED {
    // We will need to request the permission
} else {
    // The permission is granted, we can perform the action
}

This is fairly straightforward and easy to do. If we hit the else block there, that's all we need to do. Let's continue exploring actually requesting the permission and then checking if we should explain our usage of the permission:

if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED {
    // We will need to request the permission

    if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
        // Explain to the user why we need to read the storage
    }
} else {
    // The permission is granted, we can perform the action
}

We've added another simple check to see if we should provide rationale for the storage. By default, the system will only return true if the app has requested this permission previously and the user denied the request. This is a good indicator that we need to explain why the app wants the permission. However, it should not the only indicator - you should implement your own logic for this check as well.

For example, let's say the app we're making is a photography app. Asking for the camera immediately upon first launch likely requires no further explanation from the app. Users should understand it's a photography app and so using the camera makes sense. However, if we also want to include the ability to directly email the user's contacts photos taken with the app, we would want to provide a contextual explanation to the user how we're using it before we request the permission. This is an example where the system methods are not enough to rely solely upon.

Now we're checking if we have the permission, and if we don't, we're checking if the system says we should explain our usage of the permission. This section is the easiest and most direct:

if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED {
    // We will need to request the permission

    if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
        // Explain to the user why we need to read the storage
    } 

    requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);

    // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is an app-defined int constant
} else {
    // The permission is granted, we can perform the action
}

So now we request the permission. At this point, the system will display a dialog box with the permission request. Once the user accepts or denies the permission, the system then passes their decision to the app through a callback. The parameters include the request code you set (in this case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE set earlier), the permissions that were requested, and the results of each permission. Here is an example implementation:

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission was granted!
                // Perform the action
            } else {
                // Permission was denied
                // :(
                // Gracefully handle the denial
            }
            return;
        }

        // Add additional cases for other permissions you may have asked for
    }
}

At this point, we have received user input to perform an action that requires a permission, checked for the permission (showing an explanation if necessary beforehand), and handled the result callback for that.

However, we still have the WRITE_EXTERNAL_STORAGE permission we know we will need later. Simply wait for the user to perform an action that requires it, then follow all of the same steps above. Here is where permission groups kick in and provide a better experience for users: if you request the permission group and then request a permission within that group, the system will automatically approve that permission when you request it. This stops an app from overwhelming the user and provides an easier use of permission handling for developers.

This covers the basics of requesting permissions.

 

Recommended Practices

Only ask for permissions you need

You should minimize the permissions you require. If you only need the camera for a small part of the app, using an intent to achieve the functionality might be a better option than requesting the permission and chance losing that functionality if the user denies it.

 

Don't overwhelm the user

Requesting a lot of permissions at once is likely a good way to lose potential users. Only ask them for permissions when you need them.

As a caveat, if your core app is built around a permission, requesting it immediately upon launch may make more sense.

Additionally, if your app has a tutorial that explains all of the app's usages of various permissions, requesting them all at the end of the tutorial sequence might be more valid than waiting. Use your judgement.

 

Explain why you need the permissions

Any time you request a permission that is not exceedingly obvious to the user why the app needs the permission, first displaying an informative explanation of why you need the permission is a gentle way of avoiding permission denials.

 

Gracefully handle permission denial

An easy way to lose users is for apps to crash, lock up, or otherwise give the appearance of not working as expected, all of which can be caused by not handling permission denials correctly. When the user denies a permission, use your judgement on whether or not you should explain to them that the app requires the permission. Trying to perform actions that the user has denied will lead to unexpected behavior, potentially including crashes, app freezes, or UI that does not change once the interaction has completed.

For example, if the user performs an action that is clearly tied to a permission, such as taking a photo, if they deny the camera permission, you might display a popup box explaining that the app requires permission in order for them to perform the action they just tried to do (or possibly gray out the button to show that you received their denial of the permission).

Pretend the app can also geotag photos the user takes. If that permission is denied, when they go to take the photo you may instead quietly allow that denial to pass by. An even better solution here would be to offer an explanation before requesting the permission in the first place. If it's a core feature in your app, it would be best to follow the previous example and directly inform the user that the permission should be allowed.

 

SEE OUR JOB OPENINGS  

 

Topics: Coding & Development, Android Development

Kyle Sharp

Written by Kyle Sharp

Enjoy the article? Share it!