Managing Runtime Permissions in Android Applications
Android applications operate within a restricted sandbox. Accessing resources or information outside this sandbox requires requesting specific permissions. This is done by declaring the permission in the app's manifest and, for Android 6.0 (API level 23) and higher, requesting user approval at runtime.
This guide focuses on using the AndroidX libraries to check and request permissions, which simplifies compatibility with older Android versions compared to using the framework methods directly.
Declaring Permissions in the Manifest
For all Android versions, you must declare required permissions by adding <uses-permission> elements as children of the top-level <manifest> element in your AndroidManifest.xml file. For instance, an app requiring network access would include:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<uses-permission android:name="android.permission.INTERNET" />
<!-- Other permissions declared here -->
<application ...>
...
</application>
</manifest>
How the system handles the declaration depends on the permission's protection level. "Normal" permissions are granted automatically at install time. "Dangerous" permissions, which pose a greater risk to user privacy, require explicit user approval at runtime.
Checking for Permissions
If your app needs a dangerous permission, you must check for its availability every time you perform an operation that requires it. Users can revoke permissions at any time on Android 6.0 and later, so a previous grant is no guarantee of current access.
To verify permission status, use the ContextCompat.checkSelfPermission() method. The following example checks if the app has permission to access the device's camera:
Java:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted
}
Kotlin:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted
}
If the permission is granted, the method returns PackageManager.PERMISSION_GRANTED. If not, it returns PackageManager.PERMISSION_DENIED, and your app must explicitly ask the user for permission.
Requesting Permissions from the User
When checkSelfPermission() returns PERMISSION_DENIED, your app should prompt the user. Android provides methods like ActivityCompat.requestPermissions() to display a standard system dialog for this purpose.
Providing a Rationale
In some cases, itt's helpful to explain why you're app needs a permission before asking for it. Use the ActivityCompat.shouldShowRequestPermissionRationale() method to determine if an explanation is warranted. This method returns true if the user has previously denied the request. It returns false if the user denied it and selected "Don't ask again," or if a device policy prohibits the permission.
Making the Permission Request
If the permission is not granted and no rationale needs to be shown (or after showing one), call requestPermissions(). You must pass the permissions you need and an app-defined integer request code to identify this specific request.
The code below demonstrates the complete flow for requesting access to the device's location:
Java:
// thisActivity refers to the current Activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an educational UI to the user asynchronously.
// After the user acknowledges, re-attempt the permission request.
} else {
// Request the permission directly
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
APP_LOCATION_PERMISSION_REQUEST_CODE);
}
} else {
// Permission is already available
}
Kotlin:
// thisActivity refers to the current Activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an educational UI to the user asynchronously.
// After the user acknowledges, re-attempt the permission request.
} else {
// Request the permission directly
ActivityCompat.requestPermissions(thisActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
APP_LOCATION_PERMISSION_REQUEST_CODE)
}
} else {
// Permission is already available
}
Note: The system dialog describes the permission group (e.g., "Location") your app needs, not the specific permission constant.
Handling the User's Response
After the user responds to the permission dialog, the system invokes the onRequestPermissionsResult() callback in your Activity or Fragment. You must override this method to handle the result, using the request code to identify which request this response corresponds to.
Java:
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == APP_LOCATION_PERMISSION_REQUEST_CODE) {
// Check if the request was cancelled or if any permission was granted.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted. Proceed with the location-dependent task.
} else {
// Permission denied. Disable functionality that requires it.
}
}
// Handle other request codes here if your app makes multiple requests.
}
Kotlin:
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
APP_LOCATION_PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted. Proceed with the location-dependent task.
} else {
// Permission denied. Disable functionality that requires it.
}
}
// Add other 'when' branches for additional request codes.
}
}
If the user grants a permission, the system may automatically grant other permissions in the same group that you've declared in your manifest. However, your code should always request each specific permission it needs and not assume permissions are granted based on group membership.
If the user denies the permission and selects "Don't ask again," subsequent calls to requestPermissions() for that permission will be automatically denied, and onRequestPermissionsResult() will receive PERMISSION_DENIED without showing a dialog.
Declaring Permissions for Specific API Levels
To declare a permission only for devices that support runtime permissions (API level 23+), use the <uses-permission-sdk-23> element instead of <uses-permission>. You can also use the android:maxSdkVersion attribute with either element to specify that a permission is not required on devices running a higher Android version.
<uses-permission-sdk-23 android:name="android.permission.CAMERA" />
<!-- Or -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />