A camera dialog is the first thing many users see after installing a photo app — and, for too many other apps, it is also the first thing they see when they open a shopping list. Permissions are where feature requirements meet privacy expectations, and the system dialog is unforgiving: it appears on top of your carefully designed screen, in a font you did not choose, and it can be dismissed with a swipe.
Underneath that dialog sits a surprising amount of machinery. Android separates install-time, runtime and special permissions; the same request behaves differently across Android versions; and a denial changes what the next request does.
Android app permissions are also a product decision. Every permission you declare is visible on your store listing, every request interrupts a task, and every one you mishandle becomes a support ticket or a policy question. This guide covers what permissions are, how to request runtime access, what happens when users deny or revoke it, how to test each state, and how permissions relate to Google Play's Data safety declarations.
What Are Android App Permissions?
Every Android app runs in a sandbox. Anything outside that sandbox — the camera, the microphone, the user's contacts, the device's location, files created by other apps — is reachable only through an interface the system controls. Permissions are that control. In Google's own framing they protect two things: restricted data, such as system state and contact information, and restricted actions, such as connecting to a paired device or recording audio.
In one sentence: a permission is the system's way of asking the user to approve one specific kind of access, on behalf of one specific app — and it can be granted, limited, denied or revoked at any time.
Google states three goals for the system: control (the user decides what they share), transparency (the user understands what is accessed and why) and data minimization (the app accesses only what a task requires). Those goals explain most of the guidance that follows, and two consequences are easy to forget:
- A declaration is not a grant. Writing
<uses-permission>inAndroidManifest.xmltells the system and the store that your app may ask. It gives the app nothing. - A grant is not permanent. Users can revoke access in system settings, the system can reset permissions for apps that go unused, and one-time grants expire on their own.
Types of Android Permissions
Android groups permissions by protection level, and the level determines how access is granted. Install-time permissions are granted automatically when the app is installed; they include normal permissions, which carry little privacy risk, and signature permissions, granted only to apps signed with the same certificate as the app or OS that defines them. Runtime permissions carry the dangerous protection level and need an explicit user decision. Special permissions carry appop and are toggled on the Special app access page — no dialog exists for them.
| Type | What it means | Typical example | Developer consideration |
|---|---|---|---|
| Normal | Install-time permission with very little privacy risk, granted automatically at install | INTERNET, VIBRATE |
No request code is needed, but it still appears in the store listing and in manifest review |
| Runtime (dangerous) | Sensitive access the user approves at runtime through a system dialog | CAMERA, ACCESS_FINE_LOCATION, POST_NOTIFICATIONS |
Check the status before every access; never assume an earlier grant still holds |
| Signature | Granted only when the app is signed with the same certificate as the app or OS that defines it | Service-binding permissions used by privileged services | Some signature permissions are not available to third-party apps at all — check before designing around one |
| Special | Powerful operations the user enables from a settings page; there is no permission dialog | Exact alarms, drawing over other apps, all-files access | Each has its own check method; re-check when the app resumes, and expect policy scrutiny |
One clarification prevents a lot of confusion: permission groups are not permission types. Groups exist so the system can show fewer dialogs for closely related permissions, and Google warns that permissions can change groups without notice — so never build logic that assumes two permissions share a group.
How Android Runtime Permissions Work
Runtime requests exist because of Android 6.0 (API level 23). If your app declares a dangerous permission and runs on API 23 or higher, you must request it at runtime. On API 22 and lower, declared permissions are granted at install with no dialog — which is why older tutorials look different from current ones.
The documented workflow has eight steps, and the order carries most of the value:
- Evaluate whether the feature can be built without the permission at all — many use cases can.
- Declare the permission in the manifest, if you conclude you need it.
- Design the flow so a specific user action maps to a specific permission.
- Wait for the user to invoke that action.
- Check whether the permission is already granted, immediately before using the protected API.
- Explain the purpose in-app when the system indicates that a rationale is appropriate.
- Request the permission, which shows the system dialog.
- Handle the answer — use the feature, or degrade gracefully.
Important: a permission declaration does not mean the user has granted access. Google's documentation is explicit that you must check the status every time you perform an operation that requires it.
The check is a single call. ContextCompat.checkSelfPermission() returns PERMISSION_GRANTED or PERMISSION_DENIED, and the RequestPermission contract from androidx.activity handles the request and its callback without manual request codes:
// Kotlin — Activity (or Fragment), androidx.activity 1.2.0+
private val requestCamera = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera() else showCameraUnavailable()
}
private fun onCapturePhotoClicked() {
val granted = ContextCompat.checkSelfPermission(
this, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
when {
granted -> openCamera()
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) ->
showCameraRationale { requestCamera.launch(Manifest.permission.CAMERA) }
else -> requestCamera.launch(Manifest.permission.CAMERA)
}
}
Two subtleties matter before you copy that pattern into a large codebase. First, shouldShowRequestPermissionRationale() returns false both when you have never asked and when the system has decided the dialog should no longer appear — it cannot tell those apart, so a good denial experience usually records whether the permission was already requested.
Second, the rationale is a courtesy, not a checkpoint. Google's guidance is to explain in context, keep a cancel option, and never block the user. A full-screen explainer before every request puts your dialog in front of the system's dialog, which is a common reason users abandon a permission flow.
When Should Your App Ask for Permissions?
Ask when the user does something that needs the access, and only then. The documented examples are straightforward:
- Ask for camera access when the user taps take a photo, not when the camera tab opens.
- Ask for microphone access when the user starts recording, for example after pressing a send-audio-message button.
- Ask for location when a location-dependent action begins — a nearby search, a check-in, a delivery address.
- Ask for notification permission when the app needs to enable notifications, and on the Android versions where that is a runtime permission.
Requesting everything at startup is the pattern Google warns against most directly: it burdens the user, invites denials, and produces prompts that cannot be understood because the user has not yet seen the feature they relate to.
What Happens When a User Denies a Permission?
Denial is a normal outcome, not an error case — and its consequences differ by Android version, so the behaviour has to be verified rather than assumed.
First denial. The feature loses access; the app keeps running. The user can usually be asked again later, but every prompt after a refusal needs a reason they can see.
Repeated denial. Starting in Android 11, if the user denies the same permission more than once during the app's lifetime of installation on that device, no dialog appears on the next request — the action is treated as "don't ask again". Google documents the USER_SET and USER_FIXED flags that adb shell dumpsys package exposes, which is how you confirm the state while testing.
One-time grants. Since Android 11 the dialog for location, microphone and camera offers Only this time. That access is temporary, so a feature must be able to request it again without crashing or re-running onboarding.
Revocation in Settings. A user can remove any permission at any moment, including while your app is running. Code that checks on every use — and re-checks when the app resumes — survives this; code that assumes the grant given during onboarding does not.
Auto-reset and reduced access. If your app targets Android 11 or higher and is unused for a few months, the system resets its runtime permissions, with the same effect as a manual denial. Separately, some permissions can be partially granted: location can be approximate, and on Android 14 and higher a user can allow selected photos only.
Graceful degradation. Google's guidance is to disable the feature that needs the permission, keep the rest of the app usable, and explain why. The same documentation carries a caution that is frequently ignored: do not link to system settings in an attempt to persuade the user. If a deep link is genuinely the only recovery path, word it neutrally and never repeat it as a nag.
Developer tip: re-check permission status when users return from Android Settings — for example in onResume(). A user who grants access there expects the waiting feature to work on return, not after a restart.
Common Android Permissions and How to Test Them
The exact permission name, the dialog wording and the available choices depend on the Android version the device runs, so treat the list below as the scenarios to cover rather than a fixed API contract.
| Permission or feature | Typical use case | What to test | Common failure scenario |
|---|---|---|---|
| Camera | Capturing a photo or scanning a document inside the app | Grant, deny, revoke mid-session, hardware absent | Assuming a grant, or a security exception on a declared-but-denied camera permission |
| Microphone | Voice notes, dictation, audio recording | Grant, deny, only-this-time, denial while recording | Recording state left active after a denial, or a silent empty file |
| Location | Store finder, delivery address, check-in | Precise, approximate, deny, revoke, background access | A feature that breaks when only approximate location is granted |
| Notifications | Alerts, reminders, delivery updates | Allow, don't allow, swipe away, channel turned off later | Assuming notifications are visible because the permission is granted |
| Photos and media | Choosing a profile photo or a message attachment | System picker, full library access, selected photos only | Requesting broad storage access where a picker would need no permission at all |
Android Permission Testing — Practical Test Scenarios
Permission testing is not a single pass. Since Google's guidance is to test across combinations of granted and revoked permissions, the practical approach is a state machine: for each protected feature, walk every state the system can put you in, and confirm the app stays coherent.
Two techniques make this fast. Revoking from system settings reproduces a genuine user action, while adb shell pm revoke <package> <permission> and adb shell pm grant … let you jump straight to a state. Clearing app data resets the "never asked" state, which is different from a denial and should be tested separately.
| Test scenario | Expected behaviour | What the tester should verify |
|---|---|---|
| First-time request | A contextual request appears when the feature is used | Correct timing; any in-app explanation comes first and can be cancelled |
| User grants | The feature works with the access granted | The feature actually uses the permission; no stale "denied" UI remains |
| User denies | The app remains usable; only that feature is unavailable | No crash, no dead-end screen, wording says what is unavailable and why |
| Permission denied twice | On Android 11 and higher, no dialog appears on the next request | No endless retry and no misleading prompt; the state is confirmed with dumpsys package |
| Revoked in Settings | The app detects the change and recovers safely | Change noticed on resume; dependent feature disabled instead of throwing |
| App restarted | Permission state is read again at launch | No stale assumption carried over from the previous session |
| One-time grant | Temporary access ends when the app leaves the foreground | The feature re-checks on next use instead of failing silently |
| Limited or approximate access | The app handles reduced data without error | Approximate location and selected photos still produce a usable result |
| Auto-reset app | Permissions are cleared as if the permission were denied | Onboarding does not loop; the request path still reaches a working state |
| Device and OS variation | Features behave correctly for the platform under test | Cover an API 23 device, a recent release and at least one second manufacturer |
| Third-party SDK request | The prompt appears in your app's name, triggered by the SDK's feature | You can name the SDK that caused it; a denial does not break unrelated features |
Real-device coverage matters here because permission behaviour is one of the few things that can differ between a clean emulator image and a device with a manufacturer's own settings screens. Android real device testing covers how to choose that matrix, and a structured bug report turns "the camera doesn't work" into a reproducible permission state.
How to Reduce Unnecessary Permission Requests
Google's recommendation is to minimize permission usage, not to optimize the prompt for a permission you did not need. In practice that means auditing in four places:
- The manifest. Remove permissions declared for features you no longer ship, and set
android:maxSdkVersionwhere a permission is only needed for older releases. - Your dependencies. Adding a library inherits its permission requirements, and the prompt still appears under your app's name. Review what each SDK declares and prefer SDKs that need less.
- The request itself. Ask for the lowest fidelity that satisfies the use case — coarse rather than precise location — and the shortest duration that works.
- The design. Replace broad access with a system picker or an intent wherever the system already offers one.
A concrete example: a profile-photo upload. The obvious implementation requests access to the media library. The better one launches the system photo picker, which requires no runtime permission; the system grants temporary read access to the files the user selected. Where the picker is unavailable, the AndroidX contract falls back to ACTION_OPEN_DOCUMENT — still permission-free.
The same applies to capture. To let the user take a photo with the pre-installed camera app, use ACTION_IMAGE_CAPTURE rather than declaring CAMERA — and note the corollary: if you do declare CAMERA and the user denies it, that intent then fails with a SecurityException. A declaration you do not need makes the app more fragile.
Two more details are easy to miss. Declaring a hardware-associated permission can silently filter your app from devices: without <uses-feature android:required="false"> for that hardware, the store may treat it as required and hide your app from devices that lack it. And special permissions deserve their own audit — overlay access, all-files access and exact alarms are powerful, are reviewed under policy, and are usually replaceable with a standard API.
Android App Permissions and Google Play Data Safety
Permissions and Data safety are related but not interchangeable, and the difference is where most declaration mistakes happen. A permission describes what the platform allows your app to attempt. Data safety describes what your app, and the code you ship inside it, actually collects or shares — including through third-party SDKs.
| Area | What developers need to review |
|---|---|
| Manifest | Declared permissions, including those merged in from libraries, and whether each one still maps to a feature you ship |
| App code | Actual data access, what leaves the device, whether it is linked to a user, and how long it is kept |
| Third-party SDKs | What they collect, whether they share it, and whether a provider profiles users across customers — that is "sharing", not a service provider |
| Data safety form | One global declaration per package name, covering every version currently distributed — it must match reality, and you are accountable for it |
| Privacy policy | Disclosures that agree with the Data safety form and with what the app really does, including third-party code |
Important: the Data safety form is not generated from your manifest. It is a statement you write, and Google states plainly that only you possess the information required to complete it accurately.
Play's User Data policy adds two requirements that catch teams out. Any handling of personal and sensitive user data must be disclosed, and the policy directs developers to request a runtime permission whenever one is available. A manifest entry without a runtime request is therefore both a functional bug and a policy risk.
Common Permission Handling Mistakes
| Problem | Better approach |
|---|---|
| Requesting permissions at startup | Request when the user invokes the feature that needs the access |
| Declaring permissions the app never uses | Audit the merged manifest, then delete or bound declarations you do not need |
| Never explaining the purpose | Say what is accessed and what stops working without it, at the moment of the request |
| Assuming a permission is still granted | Check before each use and re-check on resume; grants can be revoked at any time |
| Crashing after a denial | Treat denial as an expected branch; disable the feature instead of the screen |
| Ignoring a return from Settings | Re-check in onResume() and continue the flow the user was in |
| Ignoring SDKs that request access | Map each prompt to the SDK that triggers it, and disclose that SDK's data use |
| Making the app useless without a grant | Offer an alternative path — manual entry, a picker, or a read-only version of the feature |
| Inaccurate privacy disclosures | Keep the Data safety form, the in-app policy and the actual behaviour in agreement |
Complete Android Permission Testing Checklist
The list below works as a pre-release pass for a single feature, and as an audit before a major version.
- Review every permission in the merged
AndroidManifest.xml, including library entries. - Confirm each permission maps to a feature you still ship, and delete the rest.
- Write down the purpose of each remaining permission in one sentence a user would understand.
- Test the first-time request in context, on a build with cleared app data.
- Test the grant path, and confirm the feature works afterwards.
- Test the denial path, and confirm the app stays usable.
- Test a second denial on Android 11 or higher, where the dialog stops appearing.
- Revoke the permission in system settings and return to the app.
- Restart the app and confirm the state is read again, not remembered.
- Test one-time access, and the next use after it expires.
- Test approximate location and selected-photos access where they apply.
- Repeat on the Android versions and device configurations you support.
- Verify each feature's fallback: what the user sees without the permission.
- Compare the Data safety declarations and privacy policy with what the app now does.
Frequently Asked Questions
What are Android app permissions?
They are the system's controls over access to restricted data and restricted actions — the camera, microphone, location, notifications, contacts and so on. An app declares what it may request, and the user decides what it actually gets.
What is the difference between normal and runtime permissions?
Normal permissions are install-time permissions with little privacy risk: they are granted automatically when the app is installed and produce no dialog. Runtime permissions, also called dangerous permissions, are granted only after the user approves a system dialog at runtime.
When should an Android app request permissions?
When the user starts the action that needs the access. Requesting everything at launch is the pattern Google advises against, because the prompts arrive without context and invite denial.
What happens if a user denies a permission?
The protected feature loses access while the rest of the app continues to work. On Android 11 and higher, denying the same permission twice means the dialog is not shown again for that installation; the user can still change it in system settings.
Can an Android app work without camera or location permission?
Yes for most use cases. A photo can be captured by another camera app through an intent, media can be chosen with the system photo picker, and location-dependent features can offer manual address entry or a location button instead of a broad permission.
How do developers test permission-denial scenarios?
By walking each permission state deliberately: first request, grant, denial, second denial, revocation in settings, app restart, one-time access and reduced access. adb shell pm revoke and adb shell dumpsys package make the states reproducible.
Why should developers minimize permission requests?
Each request interrupts the user, each denial costs a feature, and every declared permission has to be justified in review and in your data disclosures. Fewer permissions means less friction, fewer failure paths and less policy exposure.
Are Android permissions the same as Google Play Data safety declarations?
No. Permissions describe what the platform lets the app attempt. Data safety describes what the app and its SDKs actually collect or share, including data obtained without a permission, such as an email address typed into a form.
Can third-party SDKs introduce additional permission requirements?
Yes. Adding a library inherits its permission declarations, and the resulting prompt still appears under your app's name. Those permissions, and the data the SDK handles, also belong in your Data safety declaration.
Official Android and Google Play Resources
- Permissions on Android — protection levels, permission groups and core best practices
- Request runtime permissions — the request workflow, rationale checks and result handling
- Declare app permissions — manifest declarations and optional hardware features
- Request special permissions — the Settings-based flow and per-permission checks
- Best practices for app permissions — library review, denial handling and testing across permission states
- Minimize permission requests — pickers, location alternatives and permission-free approaches
- Photo picker — permission-free media selection with Activity result contracts
- Provide information for Google Play's Data safety section — what the form covers and how sharing is defined
- Google Play User Data policy — disclosure, consent and third-party SDK responsibilities
Conclusion
Permission handling is one of the few parts of Android development where correctness is visible to the user. A prompt at the wrong moment, a screen that dies after a denial, or a declaration that contradicts the privacy policy are failures the user experiences directly — and none of them appear in a happy-path test.
The pattern that holds up is unglamorous: request less, ask in context, check every time, treat denial as a designed outcome, and test the states rather than the happy path. If you are validating this before a closed test, the pre-launch report will surface crashes that only occur under real conditions, and a structured permission test matrix turns the rest into evidence. TestersHQ works with Android teams preparing for Google Play release, where permission flows get exercised by real testers on real devices rather than by a checklist alone.