Android Runtime Permissions Request Tutorial


Android Runtime Permissions - Featured

Android M introduced a new concept of permissions model with its release. According to this new concept now apps would have to request permissions from the users at run time. In a way this is a very useful feature for the end users. Making their user experience very secure on an Android device. But inversely this feature imposes a great deal of effort on development. As the new permissions model also allows the user to deny the permission at runtime. Therefore as developers, we would have to handle all the use cases. Thankfully, requesting Android runtime permissions, is not that difficult, with a few lines of code we can build a full fledged permission handling mechanism. But before doing so lets understand the permission model first.

Understanding Android Run Time Permissions Model

Android system permissions are divided into many categories. But when it comes to permission request at runtime from Android 6.0 onward, system permissions are categorized into two categories. Normal and Dangerous, to access either of these two permissions, the first step is to declare them in the manifest, as shown below. The difference between these two is that, dangerous permissions are now granted by the user at run time. Otherwise your app would crash with security exception. On the other hand we don’t need to request permissions falling under the normal category.

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.truiton.runtimepermissions"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

In the above android manifest, first permission android.permission.INTERNET is a normal permission and other two android.permission.READ_CONTACTS, android.permission.WRITE_EXTERNAL_STORAGE are dangerous permissions. Therefore in the following example, we would request the user to grant the later two Android permissions at runtime.

Android Runtime Permissions

There is another special category of permissions having permissions like SYSTEM_ALERT_WINDOW and WRITE_SETTINGS. Although these permissions are neither categorized in normal or dangerous section, but still it is required that these are requested by user at runtime by firing an intent (not discussed in this article).

Requesting Android Runtime Permissions

From Android 6.0 onward, declaring a permission in manifest will not enable an app to use that permission. In a way its just a way to notify the system that your app can use this permission. But to actually use the permission you would have to make an Android runtime permission request. As mentioned above, in this example we would be requesting two permissions android.permission.READ_CONTACTS, and android.permission.WRITE_EXTERNAL_STORAGE. Usually a permission should be requested only when it is required. Hence when a permission is required in your code, please initiate the permission request process by using the following code:

    if (ContextCompat.checkSelfPermission(MainActivity.this,
    Manifest.permission.WRITE_EXTERNAL_STORAGE) + ContextCompat
            .checkSelfPermission(MainActivity.this,
    Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale
                (MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
                ActivityCompat.shouldShowRequestPermissionRationale
                        (MainActivity.this, Manifest.permission.READ_CONTACTS)) {
            Snackbar.make(findViewById(android.R.id.content),
                    "Please Grant Permissions",
                    Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[]{Manifest.permission
                                            .WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_CONTACTS},
                                    REQUEST_PERMISSIONS);
                        }
                    }).show();
        } else {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission
                            .WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_CONTACTS},
                    REQUEST_PERMISSIONS);
        }
    } else {
        //Call whatever you want
        myMethod();
    }

This would initiate an Android runtime permissions request. If you have the permission, myMethod() would be called. If you have been denied or granted the permission by user action, then the onRequestPermissionResult() method would be called:

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_PERMISSIONS: {
                if ((grantResults.length > 0) && (grantResults[0] +
                        grantResults[1]) == PackageManager.PERMISSION_GRANTED) {
                    //Call whatever you want
                    myMethod();
                } else {
                    Snackbar.make(findViewById(android.R.id.content), "Enable Permissions from settings",
                            Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                            new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    Intent intent = new Intent();
                                    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                    intent.addCategory(Intent.CATEGORY_DEFAULT);
                                    intent.setData(Uri.parse("package:" + getPackageName()));
                                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                                    startActivity(intent);
                                }
                            }).show();
                }
                return;
            }
        }
    }

This is an additional method which you need to implement in your activity, to receive the Android runtime permissions request callback. Once the callback is received in the above method, and if the permission is granted you can call the same myMethod() to invoke the desired functionality. Also here in the above method we have taken care of the situation in which the permission is not granted. We would ask the user to launch the settings screen and enable permissions manually.

Android M Runtime Permissions – Design Pattern

By going through the above code you must have understood, requesting Android runtime permissions is very easy. But the implementation of this methodology in your existing code base could be a very time taking activity. Therefore a design pattern is required, where we can write the permission request code once and use it again and again.

To do so lets design an Abstract activity class, where the onRequestPermissionResult() method can be implemented. This would enable us to deliver permission granted callbacks to the child activity by using an abstract method, resulting minimal code change.

package com.truiton.runtimepermissions;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.SparseIntArray;
import android.view.View;

/**
 * Created by MG on 03-04-2016.
 */
public abstract class RuntimePermissionsActivity extends AppCompatActivity {
    private SparseIntArray mErrorString;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mErrorString = new SparseIntArray();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        int permissionCheck = PackageManager.PERMISSION_GRANTED;
        for (int permission : grantResults) {
            permissionCheck = permissionCheck + permission;
        }
        if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) {
            onPermissionsGranted(requestCode);
        } else {
            Snackbar.make(findViewById(android.R.id.content), mErrorString.get(requestCode),
                    Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Intent intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            intent.addCategory(Intent.CATEGORY_DEFAULT);
                            intent.setData(Uri.parse("package:" + getPackageName()));
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                            startActivity(intent);
                        }
                    }).show();
        }
    }

    public void requestAppPermissions(final String[] requestedPermissions,
                                      final int stringId, final int requestCode) {
        mErrorString.put(requestCode, stringId);
        int permissionCheck = PackageManager.PERMISSION_GRANTED;
        boolean shouldShowRequestPermissionRationale = false;
        for (String permission : requestedPermissions) {
            permissionCheck = permissionCheck + ContextCompat.checkSelfPermission(this, permission);
            shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
        }
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            if (shouldShowRequestPermissionRationale) {
                Snackbar.make(findViewById(android.R.id.content), stringId,
                        Snackbar.LENGTH_INDEFINITE).setAction("GRANT",
                        new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                ActivityCompat.requestPermissions(RuntimePermissionsActivity.this, requestedPermissions, requestCode);
                            }
                        }).show();
            } else {
                ActivityCompat.requestPermissions(this, requestedPermissions, requestCode);
            }
        } else {
            onPermissionsGranted(requestCode);
        }
    }

    public abstract void onPermissionsGranted(int requestCode);
}

To request Android runtime permissions we would have to extend the above activity and call its requestAppPermissions() method, as shown below:

package com.truiton.runtimepermissions;

import android.Manifest;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends RuntimePermissionsActivity {

    private static final int REQUEST_PERMISSIONS = 20;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MainActivity.super.requestAppPermissions(new
                                String[]{Manifest.permission.READ_CONTACTS,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE}, R.string
                                .runtime_permissions_txt
                        , REQUEST_PERMISSIONS);
            }
        });
    }

    @Override
    public void onPermissionsGranted(final int requestCode) {
        Toast.makeText(this, "Permissions Received.", Toast.LENGTH_LONG).show();
    }
}

In the above piece of code, we took the full advantage of an abstract class. Where we made all Android run time permission requests in the parent class, and called the abstract method onPermissionsGranted() of that class, to return callbacks to the child activity. Full source code available here:

Source Code

This way, when implementing Android runtime permissions model, to support Android M and above, minimal code needs to be written in the existing activities. As the permission request would be made through the parent class method requestAppPermissions(), and results would be propagated back by the onPermissionsGranted() method. Although if you have already implemented a base activity pattern in your app, implementing this pattern would be very easy, as the same class would be reused to implement this pattern. Hope this helped. For more updates please connect with us on Facebook, Twitter and Google+.

About Mohit Gupt

An android enthusiast, and an iPhone user with a keen interest in development of innovative applications.


Leave a comment

Your email address will not be published. Required fields are marked *

5 thoughts on “Android Runtime Permissions Request Tutorial