Understanding App Permissions
Overview
By default, an Android app starts with zero permissions granted to it. When the app needs to use any of the protected features of the device (sending network requests, accessing the camera, sending an SMS, etc) it must obtain the appropriate permission from the user to do so.
Before Marshmallow, permissions were handled at install-time and specified in the AndroidManifest.xml within the project. Full list of permissions can be found here. After Marshmallow, permissions must now be requested at runtime before being used. AndroidX ships first-party support for this through the Activity Result API (registerForActivityResult with the RequestPermission contract), which replaced both the older requestPermissions()/onRequestPermissionsResult() pattern and the third-party permission-helper libraries that used to fill this gap — see the Runtime Permissions section below.
Permissions before Marshmallow
Permissions were much simpler before Marshmallow (API 23). All permissions were handled at install-time. When a user went to install an app from the Google Play Store, the user was presented a list of permissions that the app required (some people referred to this as a “wall of permissions”). The user could either accept all the permissions and continue to install the app or decide not to install the app. It was an all or nothing approach. There was no way to grant only certain permissions to the app and no way for the user to revoke certain permissions after the app was installed.
Example of pre-Marshmallow permissions requested by the Dropbox app:

For an app developer, permissions were very simple. To request one of the many permissions, simply specify it in the AndroidManifest.xml:
For example, an application that needs to read the user’s contacts would add the following to its AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.READ_CONTACTS" />
...
</manifest>
That’s all there was to it. The user had no way of changing permissions, even after installing the app. This made it easy for developers to deal with permissions, but wasn’t the best user experience.
Permission Updates in Marshmallow
Marshmallow brought large changes to the permissions model. It introduced the concept of runtime permissions. These are permissions that are requested while the app is running (instead of before the app is installed). These permission can then be allowed or denied by the user. For approved permissions, these can also be revoked at a later time.
This means there are a couple more things to consider when working with permissions for a Marshmallow app. Keep in mind that your targetSdkVersion must be >= 23 and your emulator / device must be running Marshmallow to see the new permissions model. If this isn’t the case, see the backwards compatibility section to understand how permissions will behave on your configuration.
Normal Permissions
When you need to add a new permission, first check this page to see if the permission is considered a PROTECTION_NORMAL permission. In Marshmallow, Google has designated certain permissions to be “safe” and called these “Normal Permissions”. These are things like ACCESS_NETWORK_STATE, INTERNET, etc. which can’t do much harm. Normal permissions are automatically granted at install time and never prompt the user asking for permission.
Important: Normal Permissions must be added to the AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
Runtime Permissions
If the permission you need to add isn’t listed under the normal permissions, you’ll need to deal with “Runtime Permissions”. Runtime permissions are permissions that are requested as they are needed while the app is running. These permissions will show a dialog to the user, similar to the following one:

The first step when adding a “Runtime Permission” is to add it to the AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codepath.androidpermissionsdemo" >
<uses-permission android:name="android.permission.READ_CONTACTS" />
...
</manifest>
Next, you’ll need to initiate the permission request and handle the result. The recommended way to do this is the Activity Result API: you register an ActivityResultLauncher with the ActivityResultContracts.RequestPermission contract, launch it when the feature needs the permission, and handle the user’s decision in the registered callback. The system manages the request code for you, so the older requestPermissions(...) / onRequestPermissionsResult(...) pattern with hand-rolled request-code constants is no longer needed. The flow keeps the same four steps: check whether you already hold the permission, show a rationale if the user previously denied it, request the permission, and handle the result.
The following code shows how to do this in the context of an Activity (it requires androidx.activity 1.2.0+ / androidx.fragment 1.3.0+, which any current AppCompat setup already includes), but this is also possible from within a Fragment.
class MainActivity : AppCompatActivity() {
// Register the launcher and the callback that handles the user's decision
// (step 4 below). Registration must happen before the activity is STARTED —
// a field initializer (as here) or onCreate() are both fine; registering
// inside a click handler or another lifecycle-late hook is too late.
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
// 4) Handle the result of the request
if (isGranted) {
Toast.makeText(this, "Read Contacts permission granted", Toast.LENGTH_SHORT).show()
} else {
// The user denied the permission. Run the feature in a degraded mode,
// and respect their decision — don't nag with repeated prompts.
Toast.makeText(this, "Read Contacts permission denied", Toast.LENGTH_SHORT).show()
}
}
// Called when the user performs an action which requires the app to read the
// user's contacts. In an actual app, request a permission at the moment the
// user triggers the feature that needs it, not blindly at startup.
private fun getPermissionToReadUserContacts() {
when {
// 1) Always check first (even if the permission was granted before),
// since the user can revoke permissions at any time through Settings.
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) ==
PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
}
// 2) If the user already denied the request once, show your own UI
// explaining why the app needs to read contacts before asking again.
ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.READ_CONTACTS) -> {
// Show an educational UI here, with a "no thanks" option, then
// call requestPermissionLauncher.launch(...) if the user agrees.
}
else -> {
// 3) Directly ask for the permission. This shows the standard
// system permission dialog, and the callback registered above
// receives the result.
requestPermissionLauncher.launch(Manifest.permission.READ_CONTACTS)
}
}
}
}
To request several permissions in one prompt, use the ActivityResultContracts.RequestMultiplePermissions contract instead — its callback receives a Map<String, Boolean> of each requested permission to its grant result.
Permission Groups
Permission Groups avoids spamming the user with a lot of permission requests while allowing the app developer to only request the minimal amount of permissions needed at any point in time.
Related permissions are grouped into one of the permission groups. When an app requests a permission that belongs to a particular permission group (i.e. READ_CONTACTS), Android asks the user about the higher level group instead (CONTACTS). This way when the app later needs the WRITE_CONTACTS permission, Android can automatically grant this itself without prompting the user.
In most of your interaction with the permission API’s you’ll be working with the individual permissions and not the permission groups, but pay close attention to what the API expects as both permissions and permission groups are Strings.
Backwards Compatibility
There are 2 main scenarios to think about when it comes to backwards compatibility:
- Your app is targeting an API less than Marshmallow (
TargetSdkVersion<23), but the emulator / device is Marshmallow:
- Your app will continue to use the old permissions model.
- All permissions listed in the
AndroidManifestwill be asked for at install time. - Users will be able to revoke permissions after the app is installed. It’s important to test this scenario since the results of certain actions without the appropriate permission can be unexpected.
- The emulator / device is running something older than Marshmallow, but you app targets Marshmallow (
TargetSdkVersion>=23):
- Your app will continue to use the old permissions model.
- All permissions listed in the
AndroidManifestwill be asked for at install time.
How to Ask For Permissions
Google recommends in this video that there are four patterns to consider when thinking about permissions:
Each pattern dictates a different way of requesting permissions. For instance, when requesting for critical but unclear permissions, use a warm welcome screen to help understand a permission is requested. For critical permissions, such as a camera app that needs camera permission, ask up-front for it. Secondary features
can be requested later in context, such as a geotagging app when asking for a location permission. For permissions that are secondary and unclear, you should include a rationale explanation if you really need them.
Storage permissions
Rethink about whether you need read/write storage permissions (i.e. android.permission.WRITE_EXTERNAL_STORAGE or android.permission.READ_EXTERNAL_STORAGE), which give you all files on the SD card. Instead, you should use methods on Context to access package-specific directories on external storage. Your app always has access to read/write to these directories, so there is no need to request permissions for it:
// Application-specific call that doesn't require external storage permissions
// Can be Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_RINGTONES,
// Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_MUSIC, or Environment.DIRECTORY_MOVIES
File dir = MyActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
Managing Permissions using ADB
Permissions can also be managed on the command-line using adb with the following commands.
Show all Android permissions:
$ adb shell pm list permissions -d -g
Dumping app permission state:
$adb shell dumpsys package com.PackageName.enterprise
Granting and revoking runtime permissions:
$adb shell pm grant com.PackageName.enterprise some.permission.NAME
$adb shell pm revoke com.PackageName.enterprise android.permission.READ_CONTACTS
Installing an app with all permissions granted:
$adb install -g myAPP.apk