Android Runtime Permissions

When i first read about android's new runtime permissions, I had a lot of questions :

  • how to ask for one ?
  • how to show user popups for rational ? does the system take care of creating a popup or is it my app's responsability ?
  • how to react when a user denies a permission ?
  • how does permission group affect asking for permission or declaring them in manifest ?
  • what is a permission protection level and what role does it have ?
  • do we need to declare/ask for permission for normal and signature permissions and which ones are they ?
  • How do i test that it works ?

    So in this blog post i will try to answer most of them :

Important rules to remember about permissions :

  • When the app needs to perform any action that requires a permission with a dangereous protection level , it should first check whether it has that permission already. If it does not, it requests to be granted that permission.

  • when user denies the permission: if it is needed for an added feature, the app can disable that feature. If the permission is essential for the app to function, the app might disable all its functionality and inform the user that they need to grant that permission.

  • because users can revoke an app's permissions at any time, you should verify that it has needed permissions before performing any action that requires a permission with a dangereous protection level.

  • If a user already has accepted a permission in a permission group and later the app asks for a permission of the same group it is automaticaly granted to it.

Permissions protection levels :

The level of access a permission is protecting :

PROTECTION_NORMAL:
are granted at install time if requested in the manifest.

PROTECTION_DANGEROUS:
must be requested in your manifest, they are not granted to your app untill the app requests them and the user accepts the request. here is a list of them in the recent preview of Android M : https://gist.github.com/pocmo/a47e93254ecac0d26695

PROTECTION_SIGNATURE :
are granted at install time if requested in the manifest and the signature of your app matches the signature of the app declaring the permissions.

Permission groups:

  • suppose an app needs the SEND_SMS and RECEIVE_SMS permissions both belong to android.permission-group.SMS and have a dangereous protection level. When the app needs to send a message, it requests the SEND_SMS permission. The system shows the user a dialog box to accept it If he agrees and Later, the app requests RECEIVE_SMS it is automatically granted because the user had already approved a permission in the same permission group.

There are only a couple of methods to understand for runtime permissions :
checkSelfPermission : Determine whether you have been granted a particular permission (Note that it doesnt request the permission it just checks if it is already granted or not) and it return PERMISSION_GRANTED if you have the permission, or PERMISSION_DENIED if not.
Remember you can’t rely on assumptions here, because even if the user has granted the permission in the past, they may have revoked it later on.

requestPermissions : Requests permissions to be granted to this application. permissions must be in your manifest, and should protection level PROTECTION_DANGEROUS. this method will prompt a dialog to the user to get their answer and then trigger your onRequestPermissionResult() callback to handle the response.

onRequestPermissionsResult : Callback for the result from requesting permissions to use it either make your Activity implements ActivityCompat.OnRequestPermissionsResultCallback (from support v4) or override the one in Activity (for v23). Remember here If the user rejected the request you’ll need to update the UI to disable the feature or indicate that it won’t be available without the permission.

shouldShowRequestPermissionRationale : Gets whether you should show UI with rationale for requesting a permission.

Tips :

  • You should minimize the number of permissions requests. If a functionality can be done using an intent instead of asking for permissions. (like an intent to take a picture with the phone's camera app instead of requesting camera's permission).

  • Don't overwhelm the user by asking only permissions at once : Instead, you should ask for a permission only as you need it.

  • Be Gentle : For the second time the user asks for a permission or after the user check dont ask again display a snackbar asking for permission instead of the popup.

  • If the context in which the permission is requested does clearly communicate to the user what would be the benefit from granting a permission don't show rationale (For example, if you write a camera app, requesting the camera permission would be expected by the user and no rationale for why it is requested is needed).

  • Use the support library v4 methods for managing permissions instead of those in revision 23 to handle pre M cases.

  • use the new <uses-permission-sdk-m> element in the app manifest to indicate that a permission is only needed on M so you can add new permissions to updated versions without forcing users to grant permissions when they install the update.

Example :

public void showCamera(View view) {  
  // Check if the Camera permission is 
  // already available.
  if (ActivityCompat.checkSelfPermission(this,  
       Manifest.permission.CAMER)
                != PackageManager.PERMISSION_GRANTED) {
            // Camera permission has not been granted.
            requestCameraPermission();
        } else {
            // Camera permissions is already available, 
            // show the camera.
            showCameraPreview();
        }
    }

    /**
     * Requests the Camera permission.
     * If the permission has been denied previously, a 
     * SnackBar will prompt the user to grant the
     * permission, otherwise it is requested directly.
     */
    private void requestCameraPermission() {                       if(ActivityCompat.
          shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            // Provide an additional rationale to the user 
            // if the permission was not granted
            // and the user would benefit from additional 
            // context for the use the permission.
            // For example if the user has previously 
            // denied the permission.
            Snackbar.make(mLayout, 
            R.string.permission_camera_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok, new 
                    View.OnClickListener() {
                        @Override
                        public void onClick(View view) {                        
                        ActivityCompat.requestPermissions(
                          MainActivity.this, 
                          new String[]
                              {Manifest.permission.CAMERA},
                                REQUEST_CAMERA);
                        }
                    })
                    .show();
        } else {
            // Camera permission has not been granted yet. 
            //Request it directly.
            ActivityCompat.requestPermissions(this, 
            new String[]
            {Manifest.permission.CAMERA}, REQUEST_CAMERA);
        }
    }
    /**
     * Callback received when a permissions request 
     * has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int 
    requestCode, @NonNull String[] permissions, @NonNull 
    int[] grantResults) {

        if (requestCode == REQUEST_CAMERA) {
            // Received permission result for camera 
            // permission.Check if the only required 
            // permission has been granted
            if (grantResults.length == 1 && 
                grantResults[0] ==  
              PackageManager.PERMISSION_GRANTED) {
                // Camera permission has been granted, 
                // preview can be displayed
                Snackbar.make(mLayout, R.string.  
                   permision_available_camera,
                        Snackbar.LENGTH_SHORT).show();
            } else {
                Snackbar.make(mLayout, 
                R.string.permissions_not_granted,
                        Snackbar.LENGTH_SHORT).show();
            }
          }
          else {
             super.onRequestPermissionsResult(requestCode, 
             permissions, grantResults);
          }
        }

Testing

List permissions and status by group:

adb shell pm list permissions -d -g  

to grant a permission :

adb shell pm grant <package_name> <permission_name>  

To revoke a permission

$ adb shell pm revoke <package_name> <permission_name>

Samples on github :

I will keep updating this blog post as i find new information