GH

Requesting dangerous permissions on Android

Back in the golden days (Android SDK versions smaller than 23), the only thing an Android application developer had to do to request permissions for his or her app was to declare them in the manifest file of their application. For example, if they wanted to use access fine location information, they would include the following in their AndroidManifest.xml file:

<manifest ...>
  <application ...>
    ...
  </application>

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

However, starting with version 23 and newer of the SDK, things have changed. Android has split the notion of permissions into non-dangerous permissions and dangerous permissions.

Pragmatic takeaway

The important pragmatic take away is that dangerous permissions must be checked, and requested if need be, by the application at run time, even though they must also be declared in the manifest. In addition, it is recommended that an application be able to degrade gracefully if the user declines to grant the permission request. What this means if you are writing an application that must access fine location information and the user denies access to fine location information is a bit unclear. I believe that in some cases there won’t be a graceful way to continue without the necessary permissions.

Checking for permissions when they’re needed

The old way of doing something like registering for location updates looked like this:

private void startLocationUpdates() {
  // Register to get location updates
  mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, mLocationListener);
}

The new way needs to look a bit more like the below, with an explicit permission check first:

private void startLocationUpdates() {
  // Check to see if we have permissions first
  if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
    PackageManager.PERMISSION_GRANTED) {

    // Register to get location updates
    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, mLocationListener);
  }
}

That doesn’t seem too bad. You’ll find that many IDEs, such as Android Studio, will complain if you forget to explicitly check for the permission first.

Registering for location updates in Android Studio with an Android SDK target of 22 set.
Registering for location updates in Android Studio with an Android SDK target of 33 set.

But what do you do to ask the user to change those permissions?

Requesting dangerous permissions

If your application needs to access dangerous permissions, you will inevitably want to ask your user to grant you those permissions. This can be done using the ActivityCompat.requestPermissions() method. We can do this using the following code:

private static final int REQUEST_FINE_LOCATION_ACCESS = 1;

private void checkPermissions() {
  // Capture if we already have the permission
  int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);

  // If we don't already have the permission, let's request it
  if (locationPermission != PackageManager.PERMISSION_GRANTED) {

    // Request the permission we want
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
            REQUEST_FINE_LOCATION_ACCESS);
  }
}

What is REQUEST_FINE_LOCATION_ACCESS? And how do we find out the result of the request? Well, the result of ActivityCompat.requestPermissions() comes back asynchronously, and we use our constant REQUEST_FINE_LOCATION_ACCESS to identify in our request handler which request we are handling. Speaking of which, how do we handle these requests? To do this, we override ActivityCompat.onRequestPermissionsResult() such as with the following code:

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {

  if (permissions.length == 0) {
    // Somewhere a cancellation occurred. Return early
    return;
  }

  switch (requestCode) {
    case REQUEST_FINE_LOCATION_ACCESS:

      for (int i = 0; i < permissions.length; i++) {
        switch (permissions[i]) {
          case Manifest.permission.ACCESS_FINE_LOCATION:
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
              // Permission has been granted!
            } else {
              // No luck, permission not granted
            }
        }
      }
  }
}

If the length of permissions array is 0, we know that the request pipeline was interrupted somewhere and there is nothing for us to do, so we return early. Otherwise, we key off of the permission request code we specified when making the permission request and go from. We get two arrays back, one with the requested permission and the other with the results.

What if they user has previously denied the permissions but there really is a google reason for them? Once again, there is a solution. This time, in the form of ActivityCompat.shouldShowRequestPermissionRationale(). Using this method, we can check before requesting permissions if a rationale for them should be displayed. If one should be shown, we can do that, otherwise, we move forward with the request. If we want to show a rationale, we can update our checkPermissions() method to the following:

private void checkPermissions() {
  // Capture if we already have the permission
  int locationPermission = ContextCompat.checkSelfPermission(this,
    Manifest.permission.ACCESS_FINE_LOCATION);

  // If we don't already have the permission, let's request it
  if (locationPermission != PackageManager.PERMISSION_GRANTED) {

    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
      Manifest.permission.ACCESS_FINE_LOCATION)) {

      // Show explanation of why the permission needs to be granted
      AlertDialog.Builder builder = new AlertDialog.Builder(this);
      builder.setMessage("Some very good reason for location access.")
            .setPositiveButton("OK", new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // Ask again now that the rationale has been shared
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                            REQUEST_FINE_LOCATION_ACCESS);
                }
                })
            .show();
    } else {

      // Request the permission we want
      ActivityCompat.requestPermissions(this,
              new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
              REQUEST_FINE_LOCATION_ACCESS);
    }
  }
}

Conclusion

Using the new permissions model for Android, things can definitely get a bit more involved than just listing your needed permissions in the manifest. Knowing how to check for permissions and request permissions is essential. Guides like this one as well as the Android Guide on Working With System Permissions can be helpful resources along the way.

Sample code

If you are interested in the sample code used for this post, you can find it on GitHub.

If you liked this post, you can share it with your followers or follow me on Twitter!