Handling Permission Denials and Device Compatibility in Android
Course: Android App Development
Topic: Handling Permission Denials and Device Compatibility
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction
Android applications often need access to sensitive device features such as the camera, microphone, contacts, storage, location, Bluetooth, and notifications. Android protects these features through a permission system that allows users to decide whether an application can access particular resources.
Handling permission denials means designing the application to respond safely and helpfully when a user refuses a permission, denies it repeatedly, or revokes it later from device settings. Device compatibility means ensuring that the application behaves correctly across different Android versions, device manufacturers, screen sizes, hardware capabilities, and system configurations.
These two areas are closely related. A feature may work on one device but fail on another because of a missing permission, unsupported hardware, a different Android API level, or manufacturer-specific behavior. Robust Android applications anticipate these differences instead of assuming every device behaves identically.
2. Learning Objectives
- Understand Android runtime permissions and permission states.
- Handle permission denial without crashing or blocking unrelated features.
- Explain why a permission is required before requesting it.
- Use Activity Result APIs to request permissions safely.
- Handle repeated denials and permissions that require opening Settings.
- Check Android versions and hardware features before using them.
- Design fallbacks for unsupported devices and unavailable features.
- Test permission behavior across Android versions and device configurations.
3. Understanding Android Permissions
Android permissions control access to protected resources. Depending on the permission and Android version, the application may need to declare it in the manifest, request approval at runtime, or direct the user to a relevant system settings screen.
| Permission Category |
Examples |
Typical Handling |
| Camera |
CAMERA |
Request when the user starts a camera-related feature. |
| Microphone |
RECORD_AUDIO |
Request before recording audio. |
| Location |
ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION |
Request only the location precision required by the feature. |
| Contacts |
READ_CONTACTS |
Explain why contact access is needed and offer an alternative when possible. |
| Notifications |
POST_NOTIFICATIONS |
On Android 13 and later, request runtime permission where applicable. |
| Bluetooth |
BLUETOOTH_SCAN, BLUETOOTH_CONNECT |
Apply the permission requirements for the Android versions targeted by the app. |
| Media access |
Image, video, and audio permissions where applicable |
Prefer the system photo picker when broad media access is unnecessary. |
Important: The required permission depends on the Android version, target SDK, operation, and implementation. Some features can use system pickers or delegated system activities without requesting broad access to user data.
4. Permission Declaration in AndroidManifest.xml
Permissions that require manifest declarations must be included in the application manifest. A declaration does not automatically grant permission to access protected resources.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:label="Permission Demo"
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Declare only the permissions the app genuinely needs. Avoid requesting a group of permissions at startup when they are unrelated to the user's current action.
5. Understanding Permission States
A permission request can produce different outcomes. The application should distinguish between a granted permission, a denied permission that can be requested again, and a situation in which the user needs to change a setting manually.
| State |
Meaning |
Recommended Response |
| Granted |
The app currently has permission. |
Continue with the requested feature. |
| Denied |
The user has refused the request. |
Explain the limitation and offer a useful alternative. |
| Denied repeatedly |
The system may stop showing the permission dialog, depending on Android version and permission behavior. |
Do not keep requesting automatically. Offer Settings only when appropriate. |
| Restricted by device or policy |
System policy, parental controls, enterprise management, or device configuration may prevent access. |
Show a clear message and avoid repeated requests. |
| Permission revoked later |
The user or system removes access after it was granted. |
Check permission again before the protected operation. |
Do not rely on a single saved Boolean value to determine whether permission is currently granted. Check the system permission state when the feature is about to use the protected resource.
6. Requesting Permissions with the Activity Result API
The Activity Result API is the recommended approach for many modern Android permission requests. It provides a lifecycle-aware callback and helps avoid older request-code patterns.
The following Kotlin example requests camera access when the user chooses to open a camera feature:
class MainActivity : AppCompatActivity() {
private val cameraPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCameraFeature()
} else {
showCameraPermissionDeniedMessage()
}
}
private fun requestCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCameraFeature()
}
else -> {
cameraPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
private fun openCameraFeature() {
// Start the camera workflow here.
}
private fun showCameraPermissionDeniedMessage() {
Toast.makeText(
this,
"Camera access was denied. You can choose another option.",
Toast.LENGTH_LONG
).show()
}
}
How it works:
- Register the permission launcher as part of the Activity or Fragment lifecycle.
- Check whether permission has already been granted.
- If it is granted, continue directly to the feature.
- If it is not granted, launch the permission request.
- Handle the result and provide a clear next step if access is denied.
7. Explaining Why a Permission Is Needed
Users are more likely to understand a permission request when the app explains its purpose at the moment the feature is used. Avoid vague messages such as “Permission required” without explaining what the permission enables.
| Feature |
Example Explanation |
| Camera |
“Camera access lets you take a profile photo. You can also select an existing image.” |
| Location |
“Location helps show nearby places. You can enter a location manually instead.” |
| Microphone |
“Microphone access is needed to record your voice message.” |
| Notifications |
“Allow notifications to receive reminders about upcoming tasks.” |
| Contacts |
“Contact access helps you select people to invite. You can enter their details manually.” |
Show the explanation before the system permission dialog when it adds meaningful context. Do not repeatedly show explanatory screens after every denial.
8. Handling Permission Denials Gracefully
8.1 Keep the Application Usable
A denial should normally disable only the feature that requires the permission. If a user denies location access, they should still be able to browse other screens, search manually, or enter an address when those options are available.
8.2 Provide an Alternative
- Camera denied: offer the system photo picker or manual image selection.
- Location denied: allow manual location entry or a map without the current-location feature.
- Notifications denied: display important information inside the app.
- Contacts denied: let the user type a name or phone number.
- Microphone denied: allow text input if the feature supports it.
8.3 Avoid Permission Loops
Do not launch a permission dialog every time a screen redraws or an Activity resumes. Trigger requests from a clear user action, track the current workflow state, and stop repeating requests after a denial.
8.4 Handle Permission Revocation
Permission can change while the app is in the background or after the user visits Settings. Check permission again before using the camera, reading location, or accessing other protected resources. If access is gone, return to the denied state safely.
9. Understanding Rationale and Repeated Denials
shouldShowRequestPermissionRationale() can help determine whether the application should explain why a permission is needed before requesting it again.
private fun handleCameraPermissionRequest() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCameraFeature()
}
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.CAMERA
) -> {
showCameraRationale {
cameraPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
else -> {
cameraPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
In this example, showCameraRationale represents an app-defined dialog or explanation screen. The actual launcher must be registered as shown in the earlier example.
Important limitations: A return value of false does not prove that the user permanently denied the permission. It can also occur before the first request. Behavior varies by permission and Android version, and some permissions have special rules. Use the permission result and the current system state together instead of relying on this method alone.
10. Opening App Settings When Necessary
If a permission cannot be requested again through the normal flow, the app may explain how to enable it from Settings. Opening Settings should be a user-initiated choice, not an automatic response to every denial.
private fun openAppSettings() {
val intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null)
)
startActivity(intent)
}
Required imports include android.content.Intent, android.net.Uri, and android.provider.Settings.
When the user returns from Settings, recheck the permission rather than assuming it was enabled:
override fun onResume() {
super.onResume()
val cameraGranted =
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
if (cameraGranted) {
// Update the UI to reflect the current permission state.
} else {
// Keep the alternative workflow available.
}
}
Do not send users to Settings for ordinary first-time denials when the normal permission request remains available. Explain what to change and let them decide whether to proceed.
11. Handling Multiple Permissions
Some workflows require more than one permission. Request only the permissions needed for the current operation, and handle partial approval correctly.
private val locationPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val coarseGranted =
permissions[Manifest.permission.ACCESS_COARSE_LOCATION]
== true
val fineGranted =
permissions[Manifest.permission.ACCESS_FINE_LOCATION]
== true
when {
fineGranted -> {
startPreciseLocationFeature()
}
coarseGranted -> {
startApproximateLocationFeature()
}
else -> {
showManualLocationOption()
}
}
}
private fun requestLocationPermissions() {
locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
}
This pattern is useful for location workflows where approximate location may be sufficient. If the user grants only approximate access, do not treat it as a total failure when the feature can work with less precision.
Permission requirements for background location, Bluetooth, media, and other features can differ by Android release. Follow the platform rules for the specific feature instead of treating all permissions as interchangeable.
12. Device Compatibility in Android
Android applications run on devices with different operating system versions, hardware specifications, display sizes, system features, and manufacturer customizations. Compatibility checks help the app select an appropriate implementation or present a fallback.
| Compatibility Area |
Potential Issue |
Recommended Approach |
| Android API level |
An API is unavailable on older releases. |
Check the API level and provide a compatible alternative. |
| Hardware features |
A device lacks a camera, GPS sensor, NFC, or Bluetooth capability. |
Check for the feature and avoid making unsupported hardware mandatory. |
| Permissions |
Permission behavior changes across Android versions. |
Use runtime checks and version-aware handling. |
| Screen size |
Controls do not fit on tablets, foldables, or smaller phones. |
Use responsive layouts and appropriate resource qualifiers. |
| Manufacturer behavior |
Battery optimization or background restrictions affect a workflow. |
Use documented Android APIs and test on real devices. |
| Connectivity |
Network access is unavailable or unreliable. |
Handle failures, retries, loading states, and offline scenarios. |
| Memory and performance |
Large images or intensive work cause slowdowns. |
Optimize resources and avoid blocking the main thread. |
13. Understanding minSdk, targetSdk, and compileSdk
- minSdk: The lowest Android API level on which the app is intended to install and run.
- targetSdk: The Android API level whose platform behavior the app declares it has been designed and tested for. Targeting a newer level can introduce additional behavior changes or permission requirements.
- compileSdk: The Android API level used to compile the application and access the corresponding API definitions.
These values serve different purposes. Raising compileSdk does not automatically make every new API available on older devices. Use runtime API checks for APIs introduced in later Android versions and verify the required permission behavior for your target SDK.
android {
compileSdk = 35
defaultConfig {
applicationId = "com.example.compatibilitydemo"
minSdk = 23
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
}
The values above are illustrative, not a universal recommendation. Choose SDK values based on your project's Android Gradle Plugin, libraries, release requirements, and current Google Play policies.
14. Checking Android API Levels
Use Build.VERSION.SDK_INT when a feature depends on an API introduced in a particular Android release.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Use Android 13+ behavior where required.
} else {
// Use the appropriate earlier-version implementation.
}
Use the API level constant that matches the feature's actual introduction version. Do not use a version check as a replacement for checking permissions or verifying that the required hardware is present.
15. Checking Hardware and Software Features
The package manager can check whether a device advertises a hardware or software feature. This is useful before offering a feature that depends on a specific capability.
val packageManager = packageManager
val hasCamera =
packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_ANY
)
if (hasCamera) {
// Enable the camera workflow.
} else {
// Hide or replace the camera action.
}
To check whether a feature is required for installation, declare it appropriately in the manifest. For optional hardware, explicitly mark the feature as not required where applicable:
<uses-feature
android:name="android.hardware.camera.any"
android:required="false" />
Optional-feature declarations can help prevent an application from being filtered out on devices that do not have that hardware. The application must still check availability at runtime before attempting to use it.
16. Handling Optional Hardware
Not every device contains the same hardware. A tablet may lack telephony hardware, a low-cost phone may have limited sensors, and a device may not support NFC.
- Check for the required feature before enabling its action.
- Hide or disable unsupported controls with an explanation.
- Offer an alternative workflow when possible.
- Handle hardware initialization failures even after a feature check.
- Release camera, sensor, and other hardware resources when they are no longer needed.
For example, an app that scans QR codes with the camera could offer manual code entry as a fallback. This improves usability and makes the application useful on devices without a compatible camera.
17. Handling Android Version-Specific Permission Changes
Android permission requirements evolve. Always verify the rules for the Android version and target SDK used by your application.
| Platform Area |
Compatibility Consideration |
| Android 6.0 and later |
Dangerous permissions generally require runtime requests when applicable, in addition to manifest declarations. |
| Android 10 and later |
Background location and storage behavior have additional restrictions that depend on the operation and target SDK. |
| Android 11 and later |
One-time permissions and additional permission restrictions affect some workflows. |
| Android 12 and later |
Bluetooth permissions and approximate location handling require attention to the relevant APIs and target SDK. |
| Android 13 and later |
Notifications use a runtime permission for apps subject to the notification permission requirement. Photo picker and media permission behavior also require version-aware implementation. |
| Android 14 and later |
Selected-photo access and other permission changes can affect media workflows. Follow the documented APIs for the selected feature. |
This table is a high-level guide, not a complete permission reference. Review official Android documentation when implementing a specific permission because behavior may depend on target SDK, device release, and the exact operation.
18. Building a Reusable Permission Helper
For larger applications, permission logic can be separated from screen-specific UI. A helper or dedicated component can check the current permission state and expose a clear result to the feature.
fun hasCameraPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
fun canUseCamera(context: Context): Boolean {
val hasHardware =
context.packageManager.hasSystemFeature(
PackageManager.FEATURE_CAMERA_ANY
)
return hasHardware && hasCameraPermission(context)
}
This helper checks two separate conditions: camera hardware availability and camera permission. It does not launch the camera itself, and a successful check does not guarantee that every camera operation will succeed. The calling feature should still handle camera initialization errors.
19. Common Mistakes and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Permission dialog never appears |
Permission was previously denied, system behavior prevents another prompt, or the code path is not executed. |
Check the current permission state, review rationale behavior, and offer Settings when appropriate. |
| App crashes after denial |
Protected API is called without verifying permission. |
Check permission before the operation and handle denial safely. |
| Feature works on one Android version only |
API or permission behavior differs across releases. |
Use API-level checks and compatible alternatives. |
| Camera button appears on unsupported device |
Hardware availability was assumed. |
Check the camera feature and handle initialization errors. |
| Location is approximate |
The user granted approximate location or device settings limit accuracy. |
Support approximate results or explain why more precise access is needed. |
| Permission was granted but access fails |
Permission was revoked, hardware is busy, service is disabled, or another error occurred. |
Recheck permission and handle resource-specific errors. |
| App is filtered from a device |
A hardware feature is declared as required when it should be optional. |
Review manifest feature declarations and installation requirements. |
| Settings changes are not reflected |
Permission state was cached and not refreshed. |
Recheck permission when returning to the foreground. |
20. Best Practices
- Request permissions only when the user starts a feature that needs them.
- Explain the benefit of the permission in plain language.
- Use the Activity Result API for permission requests.
- Check permission immediately before accessing the protected resource.
- Handle denial, partial grants, and later revocation.
- Never repeatedly prompt users after they refuse.
- Offer alternatives such as manual entry, a system picker, or a reduced-functionality mode.
- Use runtime API checks for newer platform features.
- Check optional hardware and declare manifest feature requirements accurately.
- Test on multiple Android versions, screen sizes, and real devices.
- Do not assume that emulator behavior exactly matches manufacturer devices.
- Keep permission explanations accurate and never imply that optional access is mandatory.
21. Practical Exercise: Permission-Aware Camera App
Objective: Build a simple Android screen that checks camera compatibility, requests camera permission when needed, and provides a fallback when the user declines access.
Requirements
- Create a screen with a “Use Camera” button and a “Choose Image” alternative.
- Check whether the device supports a camera.
- Request camera permission only after the user taps “Use Camera”.
- Display a helpful explanation if the permission is denied.
- Offer app settings when appropriate, without forcing the user to open Settings.
- Recheck permission when the Activity resumes.
- Handle unavailable hardware and camera initialization failures.
- Verify that the rest of the app remains usable after denial.
Expected Result
The app should open the camera feature when hardware and permission are available. If the user denies access or the device lacks a camera, it should display a clear message and keep an alternative image-selection workflow available.
22. Testing Checklist
- Test the first permission request.
- Test denial and verify that the app does not crash.
- Test denial followed by another attempt.
- Test the Settings flow where applicable.
- Grant permission in Settings and return to the app.
- Revoke permission in Settings and retry the feature.
- Test approximate versus precise location when relevant.
- Test on the oldest supported Android API level.
- Test on a recent Android release.
- Test a device or emulator without the optional hardware.
- Test with system services disabled, such as location services, where relevant.
- Test small and large screens, background/foreground transitions, and interrupted workflows.
23. Interview Questions and Answers
Q1. What is a runtime permission?
A runtime permission is a permission that an application must request from the user while the app is running, when required by Android's permission model.
Q2. Does declaring a permission in the manifest grant access?
No. A manifest declaration identifies the permission the app needs, but dangerous permissions generally also require user approval at runtime on applicable Android versions.
Q3. What should an app do if the user denies a permission?
It should avoid accessing the protected resource, explain the limitation, and offer an alternative or a user-initiated way to enable access where appropriate.
Q4. What is the purpose of shouldShowRequestPermissionRationale()?
It helps determine whether an explanatory rationale should be shown before requesting a permission again. A false result does not necessarily mean the permission was permanently denied.
Q5. What are minSdk, targetSdk, and compileSdk?
minSdk defines the minimum supported Android API level, targetSdk declares the platform behavior the app targets, and compileSdk determines the API definitions available during compilation.
Q6. How can an app check whether a device supports a camera?
Use PackageManager.hasSystemFeature() with an appropriate camera feature constant, and still handle camera initialization failures.
Q7. Why should permission be checked before each protected operation?
Users can revoke permission after initially granting it, so checking again prevents invalid assumptions and helps the app handle access changes safely.
Q8. What is a compatibility fallback?
A compatibility fallback is an alternative implementation or workflow used when a device or Android version does not support the preferred feature.
Q9. Should an app request every permission at startup?
No. It should request permissions in context when the user starts the relevant feature, minimizing unnecessary prompts and improving trust.
Q10. How should an app handle optional hardware?
Declare the feature as optional where appropriate, check for hardware at runtime, and provide an alternative when the device does not support it.
24. Quick Revision
- Manifest declarations and runtime approval are different steps.
- Use Activity Result APIs for modern permission requests.
- Check permission before accessing protected resources.
- Handle denial and revocation without crashing.
- Use rationale screens when they provide useful context.
- Offer Settings as an option when normal requests are no longer appropriate.
- Check API levels and optional hardware before using specialized features.
- Provide fallbacks for unsupported devices and denied permissions.
- Test on several Android versions and device configurations.
25. Official Learning Resources
Conclusion
Handling permission denials and device compatibility is essential for creating secure, reliable, and user-friendly Android applications. A well-designed app requests access only when needed, responds gracefully when access is refused, checks hardware and Android API availability, and provides useful alternatives. By combining runtime permission checks, clear explanations, version-aware code, and thorough testing, developers can deliver a consistent experience across a wide range of Android devices.
Explore the course: Android App Development Training
Book a demo: Register for a Course Demo
Visit JustAcademy: https://www.justacademy.co/