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.
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>
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.
14 comments
Write commentsclearly
ReplyPerfect for my requirement, thanks a ton for sharing.
ReplyYou are welcome. Thanks for the comment :-)
ReplyCan you send me source code for android flashlight on off app in marshmallow 6.0 ?
Replygvk556@gmail.com
Thanks.
Great article! Thanks! I have a question: Where do you initialize `requestPermissions`?
ReplyHi,
ReplyIt is from the ActivityCompat class.
See: ActivityCompat#requestPermissions
Thank you for the information!
Replythankyou
ReplyCan you send me source code for android flashlight on off app in marshmallow 6.0 ?
Replyapnorul@gmail.com
apnorul@gmail.com
ReplyCan you send me source code for android flashlight on off app in marshmallow 6.0 ?
Can you send me source code for android flashlight on off app in marshmallow 6.0 ?
ReplyCould you please share a code segment to populate sd card content in list view with runtime permission feature. I got lots of issue arranging different permission issue and a lengthy code.
ReplyThank you.
Hi,
ReplyI am writing an article on how to list files from SD card. I will share it within 24 hours.
Hi, please check the new article: https://www.javahelps.com/2019/04/android-list-external-storage-files.html
ReplyEmoticonEmoticon