Android Working with Runtime Permissions Request

With the release of Android 6.0 Marshmallow, Google has introduced a new concept of Android Runtime permissions model which are handled by the app. According to this new concept now apps would have to request permissions from the users at the runtime and also allows the user to deny the permission at runtime. In this tutorial, we’ll look into the new Android Runtime Permissions and learn how to handle them. If not handled properly, it can cause application crashes with the security exception.

Understanding Android Runtime Permissions

System permissions of Android 6.0 Marshmallow are divided into two categories, Normal and Dangerous. To access either of these two permissions, the first step is to declare them in the AndroidManifest. And the difference is that dangerous permissions are granted by the user at run time. If the device is running Android 5.1 Lollipop (API level 22) or lower, or the app’s targetSdkVersion is 22 or lower, the system asks the user to grant the permissions in a group which the app needs at install time, not the individual permissions. If the device is running Android 6.0 or higher, and your app’s target SDK is 23 or higher, the user can grant or deny each permission, and the app can continue to run with limited capabilities even if the user denies a permission request.

Table 1. Dangerous permissions and permission groups.

Permission Group Permission
Calendar READ_CALENDAR
WRITE_CALENDAR
Camera CAMERA
Contacts READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
Location ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
Microphone RECORD_AUDIO
Phone READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
Sensors BODY_SENSORS
phone SMS
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
Storage

READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

Let’s develop an application which checks if the permission is already present. If not, then it’s requested at runtime.

   DOWNLOAD CODE   

Creating Android Runtime Permission Project:

Refer Android beginners app development guide if you are a beginner or if you don’t know how to create a project in the android studio.

1. Create a new project in Android Studio from File ⇒ New Project and fill the project details.

2. Open build.gradle (app/build.gradle) and make sure that you have set minSdkVersion and targetSdkVersion to 23 or higher, as we have to support the permissions model.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.gadgetcreek.runtime_permissions"
        minSdkVersion 9
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.1'
    testCompile 'junit:junit:4.12'
}

Requesting Permission

3. Request the permissions at runtime, we should add it to the Manifest too, so that the device will be prompted first time when going to use the permission, and then the system will remember user’s decision until user updates from the settings. Open your AndroidManifest.xml. located under app/src. and add the CALL_PHONE and WRITE_EXTERNAL_STORAGE permission just before the application tag, this is how your manifest file should look like.

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

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

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

</manifest>

4. Open the layout file for main activity(activity_main.xml) located under src/main/res/layout/activity_main.xml and add the below layout code.

<?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:id="@+id/activity_main"
    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"
    android:background="@android:color/white"
    tools:context="com.gadgetcreek.runtime_permissions.MainActivity">

    <Button
        android:id="@+id/check_permission"
        android:layout_width="match_parent"
        android:layout_centerInParent="true"
        android:layout_height="wrap_content"
        android:text="Check Permission"/>
    <Button
        android:id="@+id/request_permission"
        android:layout_below="@+id/check_permission"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Request Permission"/>
    
</RelativeLayout>

5. Now Open the MainActivity.java file located under src/main/Java/MainActivity.java and add below code.

package com.gadgetcreek.runtime_permissions;

import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import static android.Manifest.permission.CALL_PHONE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int PERMISSION_REQUEST_CODE = 200;

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

        Button check_permission = (Button) findViewById(R.id.check_permission);
        Button request_permission = (Button) findViewById(R.id.request_permission);
        check_permission.setOnClickListener(this);
        request_permission.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        int id = v.getId();
        switch (id) {
            case R.id.check_permission:
                if (checkPermission()) {

                    Toast.makeText(this, "Permission already granted.", Toast.LENGTH_SHORT).show();

                } else {

                    Toast.makeText(this, "Please request permission.", Toast.LENGTH_SHORT).show();

                }
                break;
            case R.id.request_permission:
                if (!checkPermission()) {

                    requestPermission();

                } else {

                    Toast.makeText(this, "Permission already granted.", Toast.LENGTH_SHORT).show();

                }
                break;
        }

    }

    private boolean checkPermission() {
        int result = ContextCompat.checkSelfPermission(getApplicationContext(), CALL_PHONE);
        int result1 = ContextCompat.checkSelfPermission(getApplicationContext(), WRITE_EXTERNAL_STORAGE);

        return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermission() {

        ActivityCompat.requestPermissions(this, new String[]{CALL_PHONE, WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],@NonNull int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0) {

                    boolean phoneAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean storageAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                    if (phoneAccepted && storageAccepted)
                        Toast.makeText(this, "Permission Granted, Now you can access Phone and Storage.", Toast.LENGTH_SHORT).show();
                    else {

                        Toast.makeText(this, "Permission Denied, You cannot access Phone and Storage.", Toast.LENGTH_SHORT).show();


                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            if (shouldShowRequestPermissionRationale(CALL_PHONE)) {
                                showMessageOKCancel("You need to allow access to both the permissions",
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                                    requestPermissions(new String[]{CALL_PHONE, WRITE_EXTERNAL_STORAGE},
                                                            PERMISSION_REQUEST_CODE);
                                                }
                                            }
                                        });
                                return;
                            }
                        }

                    }
                }


                break;
        }
    }


    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(MainActivity.this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

}

In the above code, the two permissions that are checked and requested are CALL_PHONE and WRITE_EXTERNAL_STORAGE.

  • checkPermission() calls the checkSelfPermission() Method to check if the app already has permission to Call Phone and write on external storage.
  • onRequestPermissionsResult checks if the permissions are granted or not. In our code, if both the permissions are not granted an alert dialog is popped showing the mandatory need to request the permissions. To do that shouldShowRequestPermissionRationale(String permission) invoked which invokes an alert dialog showing the need for the permissions.
  • You can also revoke the permissions manually from Settings->Apps->Permissions.

For the more detailed information about permissions and their groups.

One thought on “Android Working with Runtime Permissions Request

  1. I am genuinely pleased to glance at this weblog posts which carries tons of useful
    information, thanks for providing these statistics.

Leave a Reply