The Scenario: It Works on My Old Phone!
You’ve just added <uses-permission android:name="android.permission.CAMERA" /> to your AndroidManifest.xml. You test the app on an old Android 5.1 tablet, and it works flawlessly. But the moment you run it on a modern Pixel device or a recent emulator, the app crashes before the lens even opens.
A quick peek at Logcat reveals the culprit:
java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE ... } requires android.permission.CAMERA
The Cause: Why the Manifest Isn't Enough
Before Android 6.0 (Marshmallow), permissions were a "take it or leave it" deal at install time. Users agreed to everything upfront. However, Google changed the game with the Runtime Permissions model. Now, for "Dangerous" permissions—like Camera, Location, or Contacts—simply asking in the Manifest is only half the battle.
Your app must now check for permission every single time it performs a sensitive action. If the user hasn't explicitly tapped "Allow," the system protects their privacy by throwing a SecurityException. Since nearly 99% of active Android devices now run version 6.0 or higher, mastering this flow is mandatory for any developer.
Step-by-Step Fix
Step 1: Set the Foundation in the Manifest
You still need to declare the permission. This tells the Play Store what hardware your app needs. For the camera, also include the uses-feature tag so your app doesn't show up for devices without a camera.
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<!-- Helps filter devices on the Play Store -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>
Step 2: Register the Permission Launcher
Forget the messy onRequestPermissionsResult and request codes of the past. The Activity Result API is the modern, type-safe way to handle this. Register this launcher at the top of your Activity or Fragment:
// Using Kotlin and the Activity Result API
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Success! Go ahead and launch the camera
openCamera()
} else {
// The user said no. Handle this gracefully.
Toast.makeText(this, "Camera access is required to take photos.", Toast.LENGTH_LONG).show()
}
}
Step 3: Check and Request Logic
When the user clicks your "Take Photo" button, run this check. It follows the official Android logic: check if you have it, see if you should explain why you need it, or just ask for it.
private fun checkCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
// Show an alert dialog explaining why the camera is needed
showPermissionRationaleDialog()
}
else -> {
// This triggers the system popup
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
Step 4: Launch the Camera Safely
With permissions secured, you can now trigger the camera intent without fear of a crash. Always wrap it in a try-catch block in case the device has no camera app installed.
private fun openCamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
try {
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "No camera app found", Toast.LENGTH_SHORT).show()
}
}
Verification: Testing the Flow
- **Clean Slate:** Uninstall the app from your device to clear previous choices.
- **First Run:** Trigger the camera. Ensure the system dialog appearing says "Allow [App] to take pictures?"
- **Deny Test:** Deny the permission once. Try again and ensure your "Rationale" explanation appears.
- **Final Approval:** Grant permission and verify the camera actually opens.
Prevention and Best Practices
- **Check Every Time:** Users can revoke permissions in System Settings at any moment. Never cache the permission status in a variable; always call `checkSelfPermission`.
- **Don't Over-Ask:** Only ask for the camera when the user actually taps a camera-related button. Asking for 5 permissions on the first launch is a great way to get your app uninstalled.
- **Full-Stack Security:** Remember that permissions aren't just for mobile. If your app uploads these photos to a server, you'll need correct file permissions there too. For backend configurations, tools like a [Unix Permissions Calculator](https://toolcraft.app/en/tools/developer/unix-permissions) are invaluable for ensuring your upload directories are secure but functional.
- **Graceful Failure:** If a user denies access, don't break the app. Simply disable the photo button and provide a link to the App Settings.

