Android 6.0 Runtime Permission Model

The new runtime permission model introduced in Android 6.0 is an important issue to be considered by Android developers. Until Android 6.0, as a developer you might defined the required permissions in the AndroidManifest.xml file and focus on your business logic. However, since Android 6.0 the story becomes more complex, but it provides more security and control over the application to the end user. Let's cover this problem using a simple application.


Suppose, I want to read the name of all the contacts stored in the phone, what should I do?

Step 1:
Create a new project named “Contacts Reader” with a package name “com.javahelps.contactsreader”.

Step 2:
We need to define the permission in the AndroidManifest.xml file, so request for the given permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.READ_CONTACTS"/>
After adding the permission, AndroidManifest.xml file should look like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.javahelps.contactsreader">

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

    <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">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

Step 3:
Add a ListView to the activity_main.xml layout.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/lstNames"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

Step 4:
Create an instance variable of ListView in MainActivity.java and find the object in the onCreate method.
public class MainActivity extends AppCompatActivity {
    // The ListView
    private ListView lstNames;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Find the list view
        this.lstNames = (ListView) findViewById(R.id.lstNames);

        // Read and show the contacts
        showContacts();
    }
}

Step 5:
Since the purpose of this application is not explaining “how to read contacts”, just add the following method to the MainActivity.java.
/**
 * Read the name of all the contacts.
 *
 * @return a list of names.
 */
private List<String> getContactNames() {
    List<String> contacts = new ArrayList<>();
    // Get the ContentResolver
    ContentResolver cr = getContentResolver();
    // Get the Cursor of all the contacts
    Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

    // Move the cursor to first. Also check whether the cursor is empty or not.
    if (cursor.moveToFirst()) {
        // Iterate through the cursor
        do {
            // Get the contacts name
            String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            contacts.add(name);
        } while (cursor.moveToNext());
    }
    // Close the curosor
    cursor.close();

    return contacts;
}
This method will read the names of all the contacts and return it as a List of String.

Step 6:
Add another method showContacts, which will retrieve the List of names using the above method and display it in the ListView. Also call this method from the onCreate method.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // Find the list view
    this.lstNames = (ListView) findViewById(R.id.lstNames);
    // Read and show the contacts
    showContacts();
}

/**
 * Show the contacts in the ListView.
 */
private void showContacts() {
    List<String> contacts = getContactNames();
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacts);
    lstNames.setAdapter(adapter);
}
After all these changes, the MainActivity.java should look like this:
package com.javahelps.contactsreader;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    // The ListView
    private ListView lstNames;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Find the list view
        this.lstNames = (ListView) findViewById(R.id.lstNames);
        // Read and show the contacts
        showContacts();
    }
    /**
     * Show the contacts in the ListView.
     */
    private void showContacts() {
        List<String> contacts = getContactNames();
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacts);
        lstNames.setAdapter(adapter);
    }
    /**
     * Read the name of all the contacts.
     *
     * @return a list of names.
     */
    private List<String> getContactNames() {
        List<String> contacts = new ArrayList<>();
        // Get the ContentResolver
        ContentResolver cr = getContentResolver();
        // Get the Cursor of all the contacts
        Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
        // Move the cursor to first. Also check whether the cursor is empty or not.
        if (cursor.moveToFirst()) {
            // Iterate through the cursor
            do {
                // Get the contacts name
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                contacts.add(name);
            } while (cursor.moveToNext());
        }
        // Close the curosor
        cursor.close();
        return contacts;
    }
}

Step 7:
Run the application in an emulator or Android device with Android version 5.0 or less. The application should run without any problems and list the available contact names.


Step 8:
Run the same application in an emulator or Android device with Android version 6.0 or higher and check the output.

You will get an exception like this:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.javahelps.contactsreader/com.javahelps.contactsreader.MainActivity}: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord{de57b1b 2254:com.javahelps.contactsreader/u0a54} (pid=2254, uid=10054) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
android.app.ActivityThread.-wrap11(ActivityThread.java)
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
android.os.Handler.dispatchMessage(Handler.java:102)
android.os.Looper.loop(Looper.java:148)
android.app.ActivityThread.main(ActivityThread.java:5417)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord{de57b1b 2254:com.javahelps.contactsreader/u0a54} (pid=2254, uid=10054) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
android.os.Parcel.readException(Parcel.java:1599)
android.os.Parcel.readException(Parcel.java:1552)
android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:3550)
android.app.ActivityThread.acquireProvider(ActivityThread.java:4778)
android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2018)
android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1468)
android.content.ContentResolver.query(ContentResolver.java:475)
android.content.ContentResolver.query(ContentResolver.java:434)
com.javahelps.contactsreader.MainActivity.getContactNames(MainActivity.java:51)
com.javahelps.contactsreader.MainActivity.showContacts(MainActivity.java:36)
com.javahelps.contactsreader.MainActivity.onCreate(MainActivity.java:29)
android.app.Activity.performCreate(Activity.java:6237)
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
android.app.ActivityThread.-wrap11(ActivityThread.java)
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
android.os.Handler.dispatchMessage(Handler.java:102)
android.os.Looper.loop(Looper.java:148)
android.app.ActivityThread.main(ActivityThread.java:5417)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Even though we have defined the required permission in the Manifest file, Android says that permission is denied. Actually this is the problem which has to be faced by all the Android developers, because of the runtime permission model.

In Android 6.0, the permissions are categorized into normal and dangerous permissions. As Android Developers guide defines:
Normal permissions cover areas where your app needs to access data or resources outside the app's sandbox, but where there's very little risk to the user's privacy or the operation of other apps. For example, permission to turn on the flashlight is a normal permission. If an app declares that it needs a normal permission, the system automatically grants the permission to the app.
Dangerous permissions cover areas where the app wants data or resources that involve the user's private information, or could potentially affect the user's stored data or the operation of other apps. For example, the ability to read the user's contacts is a dangerous permission. If an app declares that it needs a dangerous permission, the user has to explicitly grant the permission to the app.
Since READ_CONTACTS is a dangerous permission, we need to request the user to grant it at the runtime. Let's see, how to request the user to grant explicit permission for our application.

Step 9:
Add a new instance variable PERMISSIONS_REQUEST_READ_CONTACTS and modify the showContacts method as given below:
// Request code for READ_CONTACTS. It can be any number > 0.
private static final int PERMISSIONS_REQUEST_READ_CONTACTS = 100;

/**
 * Show the contacts in the ListView.
 */
private void showContacts() {
    // Check the SDK version and whether the permission is already granted or not.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, PERMISSIONS_REQUEST_READ_CONTACTS);
        //After this point you wait for callback in onRequestPermissionsResult(int, String[], int[]) overriden method
    } else {
        // Android version is lesser than 6.0 or the permission is already granted.
        List<String> contacts = getContactNames();
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacts);
        lstNames.setAdapter(adapter);
    }
}
Now showContacts method check the SDK version; if it is greater than Android 6.0 and if the READ_CONTACTS permission is not granted for this application, request the user for READ_CONTACTS permission. The checkSelfPermission method determines whether you have been granted a particular permission. If the version is lower than Android 6.0 or if the permission is already granted, it can continue to read the contacts.

Step 10:
In case the permission is not granted already, Android will ask the user to give the permission. The result will be passed to the MainActivity through onRequestPermissionsResult method. In this method, you need to check whether the user grants the permission or not and change the behavior of your application based on the result.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                       int[] grantResults) {
    if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission is granted
            showContacts();
        } else {
            Toast.makeText(this, "Until you grant the permission, we canot display the names", Toast.LENGTH_SHORT).show();
        }
    }
}
In this code, if the permission is granted we continue to display the contacts. If not, we simply show a warning message using Toast.
After all these modification, the MainActivit.java should look like this:
package com.javahelps.contactsreader;

import android.Manifest;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    // The ListView
    private ListView lstNames;

    // Request code for READ_CONTACTS. It can be any number > 0.
    private static final int PERMISSIONS_REQUEST_READ_CONTACTS = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Find the list view
        this.lstNames = (ListView) findViewById(R.id.lstNames);

        // Read and show the contacts
        showContacts();
    }

    /**
     * Show the contacts in the ListView.
     */
    private void showContacts() {
        // Check the SDK version and whether the permission is already granted or not.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, PERMISSIONS_REQUEST_READ_CONTACTS);
            //After this point you wait for callback in onRequestPermissionsResult(int, String[], int[]) overriden method
        } else {
            // Android version is lesser than 6.0 or the permission is already granted.
            List<String> contacts = getContactNames();
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacts);
            lstNames.setAdapter(adapter);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission is granted
                showContacts();
            } else {
                Toast.makeText(this, "Until you grant the permission, we canot display the names", Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * Read the name of all the contacts.
     *
     * @return a list of names.
     */
    private List<String> getContactNames() {
        List<String> contacts = new ArrayList<>();
        // Get the ContentResolver
        ContentResolver cr = getContentResolver();
        // Get the Cursor of all the contacts
        Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

        // Move the cursor to first. Also check whether the cursor is empty or not.
        if (cursor.moveToFirst()) {
            // Iterate through the cursor
            do {
                // Get the contacts name
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                contacts.add(name);
            } while (cursor.moveToNext());
        }
        // Close the curosor
        cursor.close();

        return contacts;
    }
}

Step 11:
Save all the changes and run the application again.


Also keep in mind, that the end user can change the granted permissions at any time in Settings.


Conclusively runtime permission model is a great feature for end users, but developers have to spend some time in upgrading their existing applications to support this model. If not, your application will not work in Android 6 or latest versions.

Find the code @ Git Hub.
Previous
Next Post »

4 comments

Write comments
Umapathi
AUTHOR
February 11, 2017 at 4:10 PM delete

Perfect for my requirement, thanks a ton for sharing.

Reply
avatar
Gobinath
AUTHOR
February 11, 2017 at 8:14 PM delete

You are welcome. Thanks for the comment :-)

Reply
avatar
Krishna
AUTHOR
April 4, 2017 at 2:41 PM delete

Can you send me source code for android flashlight on off app in marshmallow 6.0 ?
gvk556@gmail.com

Thanks.

Reply
avatar

Contact Form

Name

Email *

Message *