Dangerous Permissions: Request at Runtime

In Android 6.0 and higher devices, permissions that are considered to be dangerous not only have to be requested via <uses-permission> elements, but you also have to ask the user to grant you those permissions at runtime. What you gain, though, is that users are not bothered with these permissions at install time, and you can elect to delay asking for certain permissions until such time as the user actually does something that needs them.

Let’s explore the runtime permissions system via a new series of questions.

Along the way, we will examine bits of the ContentEditor sample module in the Sampler and SamplerJ projects. This app implements a tiny text editor, where the user can edit text from various sources. We will explore the file I/O portions of this code in an upcoming chapter. Here, we will focus on the WRITE_EXTERNAL_STORAGE permission that this app needs, as that is a dangerous permission and one that we have to request at runtime.

What Permissions Are Affected By This?

Inside Android, permissions are organized into permission groups. For example, in Android 9.0, there are ten permission groups that contain dangerous permissions:

Permission Group Permission
CALENDAR READ_CALENDAR, WRITE_CALENDAR
CALL_LOG PROCESS_OUTGOING_CALLS, READ_CALL_LOG, WRITE_CALL_LOG
CAMERA CAMERA
CONTACTS GET_ACCOUNTS, READ_CONTACTS, WRITE_CONTACTS
LOCATION ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE ADD_VOICEMAIL, ANSWER_PHONE_CALLS, CALL_PHONE, READ_PHONE_NUMBERS, READ_PHONE_STATE, USE_SIP
SENSORS BODY_SENSORS
SMS READ_SMS, RECEIVE_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH, SEND_SMS
STORAGE READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE

This roster changes over time. For example, in Android 6.0, the call log permissions were in the PHONE category.

Users will be able to revoke permissions by group, through the Settings app. They can go into the page for your app, click on Permissions, and see a list of the permission groups for which you are requesting permissions:

Dangerous Permissions Control in Android 9.0 Settings App
Dangerous Permissions Control in Android 9.0 Settings App

What Goes in the Manifest?

The same <uses-permission> elements as before. These declare the superset of all possible permissions that you can have. If you do not have a <uses-permission> element for a particular permission, you cannot ask for it at runtime, and the user cannot grant it to you.

How Do I Ask the User For Permission?

To ask the user for one of the runtime permissions — or find out that we already have it — we can use ActivityResultContracts.RequestPermission along with registerForActivityResult():

  private final ActivityResultLauncher<String> requestPerm =
    registerForActivityResult(new ActivityResultContracts.RequestPermission(),
      new ActivityResultCallback<Boolean>() {
        @Override
        public void onActivityResult(Boolean wasGranted) {
          if (wasGranted) {
            loadFromDir(Environment.getExternalStorageDirectory());
          }
          else {
            Toast.makeText(MainActivity.this, R.string.msg_sorry, Toast.LENGTH_LONG).show();
          }
        }
      });
  private val requestPerm = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
    if (it) {
      loadFromDir(Environment.getExternalStorageDirectory())
    } else {
      Toast.makeText(this, R.string.msg_sorry, Toast.LENGTH_LONG).show()
    }
  }

We can then call launch() on requestPerm when we want to request permission, supplying the name of the permission that we want to request (Manifest.permission.WRITE_EXTERNAL_STORAGE):

        requestPerm.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        requestPerm.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)

The callback will be called with a Boolean value. If it is true, we received permission; if false, we did not. Based upon that value, we can determine what we want to do.

What Do I Do If the User Says “No”?

The user can click the “Deny” button for one or more pages in that system dialog and thereby reject granting you the requested permission(s). Your callback will get false in this case, instead of true.

If you were requesting permission as a direct response to some bit of user input (e.g., user tapped on an app bar item), and the user rejects the permission you need to do the work, obviously you cannot do the work. Depending on overall flow, showing a dialog or something to explain why you cannot do what the user asked for may be needed. In some cases, you may deem it to be obvious, by virtue of the fact that the user saw the permission-request dialog and said “deny”.

If you were requesting permission pre-emptively, such as when the activity starts, you will need to decide whether that decision needs to be reflected in the current UI (e.g., “no data available” messages, disabled app bar items).

What Do I Do If the User Says “No, And Please Stop Asking”?

The second time you ask a user for a particular runtime permission, the user will have a “Don’t ask again” option:

Runtime Permission Dialog, When Redisplayed After Prior Denial
Runtime Permission Dialog, When Redisplayed After Prior Denial

If the user chooses that, not only will you not get the runtime permission now, but all future requests will immediately call your callback indicating that your request for permission was denied. The only way the user can now grant you this permission is via the Settings app.

You need to handle this situation with grace and aplomb.

Choices include:

For permissions that, when denied, leave your app in a completely useless state, you may wind up just displaying a screen on app startup that says “sorry, but this app is useless to you”, with options for the user to uninstall the app or grant you the desired permissions.

How Do I Know If the User Takes Permissions Away From Me?

Permissions granted by a user can be revoked in a few ways:

If a permission is revoked, and your app is running at the time, your process is terminated.

Hence, while your code is running, you will have all permissions that you started with, plus any new ones that the user grants on the fly based upon your request. There should be no circumstance where your process is running yet you lose a permission.

What Happens When I Ship This to an Android 5.1 or Older Device?

Older devices behave as they always have. Since you still list the permissions in the manifest, those permissions will be granted to you if the user installs the app, and the user will be notified about those permissions as part of the installation process. If you are using ActivityResultContracts.RequestPermission as described above, your code should just work.

What Happens if the User Clears My App’s Data?

If the user clears your app’s data through the Settings app, the runtime permissions are cleared as well. Behavior at this point will be as if your app had been just installed, and you will need to request the permissions.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.