Android Runtime Permissions Request

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process since the user does not need to grant permissions when they install or update the app. It also gives the user more control over the app’s functionality; for example, a user could choose to give a camera app access to the camera but not to the device location. The user can revoke the permissions at any time, by going to the app’s Settings screen.
System permissions are divided into two categories, normal and dangerous:

  • Normal permissions do not directly risk the user’s privacy. If your app lists a normal permission in its manifest, the system grants the permission automatically. See Normal Permissions
  • 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.

Dangerous permissions and permission groups.

Permission Group Permissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

On all versions of Android, your app needs to declare both the normal and the dangerous permissions it needs in its app manifest, as described in Declaring Permissions. However, the effect of that declaration is different depending on the system version and your app’s target SDK level:

  • If the device is running Android 5.1 or lower, or your app’s target SDK is 22 or lower: If you list a dangerous permission in your manifest, the user has to grant the permission when they install the app; if they do not grant the permission, the system does not install the app at all.
  • If the device is running Android 6.0 or higher, and your app’s target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running. 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.

Note: Beginning with Android 6.0 (API level 23), users can revoke permissions from any app at any time, even if the app targets a lower API level. You should test your app to verify that it behaves properly when it’s missing a needed permission, regardless of what API level your app targets.

Check For Permissions

If your app needs a dangerous permission, you must check whether you have that permission every time you perform an operation that requires that permission. The user is always free to revoke the permission, so even if the app used the camera yesterday, it can’t assume it still has that permission today.

1. Check the Target SDK Version

If the device running is former to Android 6.0, there is no need to call new runtime permission workflows.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    checkPermission();
}

2. Check if Permission Is Granted

  • Check whether required permission is granted. Call the ContextCompat.checkSelfPermission (Context, String) method, Context current context and String Permission.
  • If the application has the permission, the method returns PackageManager.PERMISSION_GRANTED and the application can proceed with the operation.
  • If the application does not have the permission, the method returns Package Manager.PERMISSION_DENIED and the application have to explicitly ask the user for permission.
    // Here, thisActivity is the current activity
    if (ContextCompat.checkSelfPermission(thisActivity,
                    Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {

3. Handle “Never Ask Again”

ShouldShowRequestPermissionRationale (String permission) can be called to determine if the user denied this permission previously or not. If this method returns true, then it’s the perfect time to tell the user exactly why the permission is needed before requesting it again. If the user denies the permission request in the past and chooses the “Never ask again” option in the permission request system dialog, this method will return false. Next time we call requestPermissions, this dialog will not appear for this kind of permission anymore. Instead, it just does nothing.

if (!shouldShowRequestPermissionRationale(
        Manifest.permission.ACCESS_FINE_LOCATION)) {
    // Explain to the user why we need to location service.
}

The result of the rational dialog will be shown when this permission is requested for the first time and also be shown if the user has ever marked that permission as “Never ask again.” For the next case, onRequestPermissionsResult() will be called with PERMISSION_DENIED without any permission grant dialog.

4. Request for Permission

If the application doesn’t have the permission which requires, the application must call requestPermissions(String [ ] permissions, int requestCode) methods to ask the appropriate permissions. Pass permission array and request code as parameters.
Example: The following code checks if the application has permission to use the location service and requests the permission if necessary.

if ((ContextCompat.checkSelfPermission(this,
        Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
    /* Ask for permission */
    // need to request permission
    if (shouldShowRequestPermissionRationale(
            Manifest.permission.ACCESS_FINE_LOCATION)) {
        // Explain to the user why we need to location service.
    }
    /* Request for permission */
    requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
            REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}

5. Handle the Permissions Request Response

If the user has granted or denied the permission request, we have to handle response and execute the functionality according to response. This can be achieved by overriding the OnRequestPermissionsResult() in the Activity where the permission was requested. This method returns the result code for granted or denied permission.

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted, Do the
                // location related task you need to do.
            } else {
            }
        }
        break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

Full Example

package org.snowcorp.myapplication;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
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.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
    private static final int PICK_IMAGE_REQUEST = 1;
    private static final int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 123;
    private Uri picUri = null;
    private ImageView mImageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mImageView = findViewById(R.id.imageView);
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    addRuntimePermission();
                } else {
                    imageBrowse();
                }
            }
        });
    }
    @TargetApi(Build.VERSION_CODES.M)
    private void addRuntimePermission() {
        if ((ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE) +
                ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
            /* Ask for permission */
            // need to request permission
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.READ_EXTERNAL_STORAGE) ||
                    ActivityCompat.shouldShowRequestPermissionRationale(this,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Snackbar.make(this.findViewById(android.R.id.content),
                        "Please Grant Permissions to upload image",
                        Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                        new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                requestPermissions(
                                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
                                                Manifest.permission.WRITE_EXTERNAL_STORAGE},
                                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                            }
                        }).show();
            } else {
                /* Request for permission */
                requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            }
        } else {
            imageBrowse();
        }
    }
    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 ) {
                boolean readPermission = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                boolean writePermission = grantResults[1] == PackageManager.PERMISSION_GRANTED;
                // permission was granted, Do the
                // location related task you need to do.
                if(readPermission && writePermission) {
                    imageBrowse();
                } else {
                    Snackbar.make(this.findViewById(android.R.id.content),
                            "Please Grant Permissions to upload profile photo",
                            Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                            new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    requestPermissions(
                                            new String[]{Manifest.permission
                                                    .READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
                                            REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                                }
                            }).show();
                }
            }
        }
        break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
    private void imageBrowse() {
        Intent galleryIntent = new Intent(Intent.ACTION_PICK,
                android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        // Start the Intent
        startActivityForResult(galleryIntent, PICK_IMAGE_REQUEST);
    }
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            if(requestCode == PICK_IMAGE_REQUEST){
                picUri = data.getData();
                mImageView.setImageURI(picUri);
            }
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

About the author

Akshay Raj

View all posts