In this guide
Android Runtime Permissions in 2026: Photo Picker, Partial Access and Foreground Services
Every Android student project that touches the camera, photos, location or notifications eventually collides with the same wall: the permission model. Android's privacy rules have changed in nearly every release since Android 6, and code copied from a two-year-old tutorial will often fail silently on a modern phone — the dialog never appears, the gallery returns nothing, the background task dies at midnight. This guide maps the current model: what must be asked at runtime, what no longer needs asking at all, and the patterns that keep student apps working on Android 13, 14 and 15.
Why the permission model keeps moving
Early Android granted everything at install time. Users had one binary choice — accept all permissions or don't install the app — so most people accepted without reading. Android 6 (2015) moved dangerous permissions to runtime dialogs. Since then, every major release has chipped away at broad access: scoped storage replaced free-roam file access, approximate location split off from precise location, background location became a separate, heavily reviewed permission, and Android 14 added partial photo access plus strict foreground-service type declarations.
The direction is consistent: apps get the minimum access they need, users can grant it temporarily or partially, and anything sensitive must be justified. Student developers feel this as friction, but it is the same friction every production app deals with. Learning the current rules now saves you from debugging permission failures as if they were code bugs.
The three kinds of permission
Install-time permissions are granted automatically at install and never show a dialog — things like internet access or vibration. Declare them in the manifest and forget them.
Runtime permissions are the dangerous ones: camera, microphone, location, contacts, and (since Android 13) media files and notifications. Your app must check and request them while running, handle denial gracefully, and survive the user revoking them later from Settings. A permission granted yesterday is not guaranteed today.
Special app access is a third category that cannot be requested with a dialog at all — the user must flip a switch in system Settings. Examples include drawing over other apps, usage access, and exact-alarm scheduling. For these, your job is to detect the missing access and deep-link the user to the right Settings page with an explanation. Requesting them without context is the fastest route to a confused user.
The runtime request flow, step by step
The modern pattern, using the Activity Result API rather than the deprecated onRequestPermissionsResult callback, looks like this:
- Declare each permission in
AndroidManifest.xml. No declaration, no dialog — the request silently fails. - Check before acting with
ContextCompat.checkSelfPermission(). Never assume. - Explain if
shouldShowRequestPermissionRationale()returns true — the user has denied once before, so show your own short explanation of why you need it before asking again. - Request with an
ActivityResultLauncherregistered forRequestPermission(single) orRequestMultiplePermissions(a set, like fine + coarse location). - Handle all three outcomes: granted (proceed), denied (disable the feature with a clear message), and permanently denied (guide the user to Settings; the system will no longer show your dialog).
One rule governs the whole flow: request the permission in context, at the moment the feature needs it — when the user taps the camera button, not on the splash screen. Apps that ask for everything up front train users to tap Deny.
A minimal launcher looks like this:
private val requestCamera = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera() else showCameraUnavailable()
}
// when the user taps the camera button:
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED -> openCamera()
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) ->
showWhyWeNeedCamera { requestCamera.launch(Manifest.permission.CAMERA) }
else -> requestCamera.launch(Manifest.permission.CAMERA)
}
The photo picker: no permission needed at all
The single biggest simplification in recent Android is the system photo picker. Instead of requesting READ_MEDIA_IMAGES and building your own gallery, you launch the picker's Activity Result contract:
private val pickImage = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri -> uri?.let { displaySelectedImage(it) } }
// usage:
pickImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
The user browses their full library in a system UI and your app receives a URI only for the chosen item — no media permission declared, no dialog, no Play policy questions. For student projects that just need "let the user choose a profile photo", the picker is the correct answer and it works back to older Android versions through the support library. Only request broad media permissions when the app genuinely needs bulk or background access to the library (a gallery app, a backup tool).
Partial media access: the Android 14 change that breaks tutorials
On Android 14+, when your app requests READ_MEDIA_IMAGES or READ_MEDIA_VIDEO, the dialog offers three options: Allow all, Allow limited access (the user picks specific items), and Don't allow. If the user picks limited access, your app receives URIs only for the selected items — and many older apps, written for the all-or-nothing world, then show an empty gallery and look broken.
Handle it properly:
- Query with the selected URIs your app actually holds; don't assume the whole library is visible.
- Re-request access when the user adds more items — Android 14 lets users expand the selection later, and your app can prompt for that when it hits an item it cannot see.
- Respect
READ_MEDIA_VISUAL_USER_SELECTED: on Android 14+, if the user granted only partial access, this is the permission flag your app actually holds. Design your gallery UI around "the items the user chose", not "all photos".
The practical takeaway for student projects: default to the photo picker (no permission), and only reach for READ_MEDIA_* when the project genuinely requires library-wide access — then test the limited-access path, because examiners and users will tap it.
Notifications need asking too (Android 13+)
Since Android 13, posting notifications requires the runtime POST_NOTIFICATIONS permission. Apps targeting API 33+ that never ask simply stay silent — FCM messages arrive but nothing appears in the tray, which students often misdiagnose as a backend problem. Request it when the user enables a feature that notifies (a reminder, an order update), not at first launch. And remember: notification permission and notification channels are separate layers — the permission gates posting at all, channels control how each type behaves.
Foreground services: declare your type (Android 14+)
If your app does ongoing work the user is aware of — music playback, a fitness tracker recording a run, a file upload that must finish — a foreground service with a persistent notification is the mechanism. Android 14 made two things mandatory:
- Declare the service type in the manifest (
camera,location,microphone,mediaPlayback,dataSync, and others). The type must match what the service actually does. - Request the matching permission at runtime — e.g.
FOREGROUND_SERVICE_LOCATIONfor a location service — in addition to the underlying data permission (ACCESS_FINE_LOCATION).
Additionally, some types (camera, microphone, location) now require the service to actually be doing that work; the system can stop services whose declared type doesn't match their behaviour. For student projects, the common traps are: starting a location-tracking service without the foreground-service permission (crash on Android 14), and using a foreground service for work that should be a WorkManager task (periodic sync, uploads) — the system will kill misused services aggressively under battery optimisation.
Background location deserves its own warning: it requires a separate runtime request, it must be requested after foreground location is granted (never bundled in the same dialog), and Play Store review treats it as a sensitive permission requiring a video demonstration of the feature. For a student project demo, foreground-only location plus a foreground service is almost always the right scope.
A migration checklist for older student apps
If your app was written against an old tutorial and misbehaves on a modern phone, work through this list:
- Target SDK updated and all dangerous permissions moved to runtime requests with the Activity Result API.
- File access migrated off broad storage: photo picker for single picks, MediaStore for the app's own media, Storage Access Framework for user-chosen documents.
- Media permission requests handle the Android 14 limited-access option.
-
POST_NOTIFICATIONSrequested in context on Android 13+. - Foreground services declare a type and request the matching
FOREGROUND_SERVICE_*permission. - No permission requested on the splash screen — every request tied to a user action.
- Denied and permanently-denied paths implemented, not just the happy path.
FAQ
Why does my permission dialog never appear on a new phone but worked on an old one?
Almost always one of three things: the permission isn't declared in the manifest, you're requesting a permission that no longer exists in that form (like READ_EXTERNAL_STORAGE on Android 13+, which was split into READ_MEDIA_*), or the user previously chose "Don't ask again" and the system is silently denying. Check the manifest, check the API level mapping, and test the Settings deep-link path.
Should I just target an old SDK to avoid all this?
No. The Play Store requires recent target SDKs for new apps and updates, and users' phones enforce the new behaviour regardless. Targeting old also forfeits the photo picker and other simplifications that make your life easier. Learn the current model once; it changes slowly now.
My app needs the camera and the gallery. Two permissions?
Use the camera permission (CAMERA) plus the system photo picker (no permission). That's one dialog instead of two, and the picker UX is better than anything you'll build in a semester. Only add READ_MEDIA_IMAGES if the project genuinely needs library-wide browsing.
How do I test the "Don't allow" and limited-access paths?
Test all three dialog outcomes on a real device or emulator running Android 14+: allow all, limited access, deny. Then revoke from Settings and re-run. Student demos almost always exercise only the happy path — the examiner tapping "Don't allow" is the most predictable demo failure there is.
Do these rules apply to apps distributed as APKs outside the Play Store?
The runtime behaviour is enforced by the OS, so yes — sideloaded apps face the same dialogs and restrictions. Play Store review policies (like the background-location video requirement) apply only to Play distribution, but the platform rules are universal.
Limitations
- Permission behaviour varies by manufacturer: aggressive battery managers on some devices kill foreground services and WorkManager jobs despite correct declarations — always test on the actual demo device.
- The photo picker and partial-access APIs depend on API level and Google Play services state; very old devices without updates fall back to legacy behaviour you may still need to handle.
- This guide covers the framework rules, not Play Store review outcomes: sensitive permissions (background location, all-files access, SMS) face human review with rejection as the default, which is out of scope for most student projects anyway.
- Android changes these rules every year — treat any permission tutorial older than about 18 months as suspect and verify against the current API level documentation.
Suitable for Android development students whose apps use the camera, photos, location or notifications and who want their projects to behave correctly on current Android versions.