Debugging log · Permissions
"SecurityException: Permission Denial" at runtime
I added the permission to the manifest, ran the app, called the API — and it crashed with SecurityException: Permission Denial. My first reaction was "but I declared it!" And that's the exact misunderstanding the modern permission model is built around: for sensitive permissions, declaring is only half the job. The user has to actually grant it, at runtime, and until they do, the call fails.
The short version: since Android 6 (API 23), "dangerous" permissions need two things — a manifest declaration and a runtime grant from the user. Declaring alone isn't enough. Check at call time, request if needed, and only act once granted.
The error
java.lang.SecurityException: Permission Denial: ... requires android.permission.CAMERA
Two kinds of permission
Android splits permissions into two groups. Normal permissions (internet access, for example) are granted automatically just by declaring them in the manifest. Dangerous permissions — camera, location, microphone, contacts, and others that touch private data or hardware — require the user's explicit consent at runtime. The manifest line is still required, but it only makes the permission requestable; it doesn't grant it. Call a protected API without the runtime grant and you get this SecurityException.
The fix — declare, check, request, then act
1. Declare it in the manifest
<uses-permission android:name="android.permission.CAMERA" />
2. Check before you use it
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
openCamera() // already granted
} else {
requestCameraLauncher.launch(Manifest.permission.CAMERA)
}
3. Request it, and react to the result
val requestCameraLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera()
else showWhyWeNeedIt() // handle denial gracefully
}
The key discipline: never assume the permission is held. Check at the moment you need it, request if it isn't, and run the protected code only from the "granted" path.
Things that cause this even when you "did it right"
- The user denied it, or revoked it later in Settings. A grant isn't permanent — check every time, not once.
- Special permissions aren't normal runtime ones. Overlay, all-files access, exact alarms and similar need their own dedicated settings flow, not
requestPermissions. - Background location is a separate, stricter request from foreground location — granting one doesn't grant the other.
- A new API level narrowed a permission. Storage and others were split into more granular permissions over releases; the old one may no longer cover what you're doing.
Handle denial as a real path, not an afterthought. If the user says no, the app shouldn't crash or loop the dialog — explain what the feature needs and let them continue without it, or send them to Settings if they want to enable it later.
Permission names above use the camera as an example — the same pattern applies to any dangerous permission.