Zygote
Storageintermediate

Scoped storage

The model that replaced the free-for-all: app-specific directories, MediaStore collections, per-file grants, and the permissions that survived — plus the ones that did not.

12 minAPI 29–37 · 17interactivequizverified 2026-10-27

Scoped storage is not one rule. It is a set of directories and APIs that replace a single broad permission with a model where an app gets its own space, an index of shared media, and explicit grants for everything else.

TL;DR#

  • An app always has free access to its app-specific directories, internal and external.
  • Shared media is reached through , which indexes photos, video and audio.
  • Documents and downloads go through the Storage Access Framework or, from API 30Android 11, a broad read grant.
  • requestLegacyExternalStorage was an opt-out in API 29Android 10; API 30Android 11 enforced the model for apps targeting 30.
  • The direction of travel is per-item consent: the photo picker and selected-photos access replace permission grants with item grants.

Mental model#

Think of a public library.

Every patron has a locker — the app-specific directory — that only they can open.

The catalogue is MediaStore: it lists what is on the shelves, and you can search it, but it does not give you the books.

To take a book out of the archive — documents, downloads — you need a librarian to fetch it and record the loan. That is the Storage Access Framework.

Where the analogy breaks#

A library's catalogue and shelves are separate. MediaStore is both: it is an index and the API through which files are opened, so "searching" and "reading" go through the same provider. And a URI grant is not a loan — it has no due date, and it can be revoked by the user at any time without the app being told until it tries to use it.

The big picture#

Loading diagram…
Text description of this diagram

A treemap of an app's storage access: its own files and cache directories, other apps' media through MediaStore, documents and downloads through the Storage Access Framework or the photo picker, and blocked paths including other apps' data and all-files access. The four groups show the shape of the scoped model: private space is free, media is indexed, documents are picked, everything else is blocked.

The four groups are also four different APIs. Choosing the wrong one is the most common scoped-storage mistake: an app that needs one document from the user does not need a storage permission at all.

Deep dive#

The app-specific directories#

Two locations, both free to use:

/data/user/0/<package>/files/          internal, private
/storage/emulated/0/Android/data/<package>/files/   external, app-specific

The internal one is removed on uninstall. The external one is also removed on uninstall, but is visible to other apps with special access (file managers, backup tools) and to the user over USB.

The distinction matters for what the user expects: a file the app manages for itself belongs in internal storage; a file the user might want to find in a file manager belongs in the external directory or in MediaStore.

MediaStore#

MediaStore is a content provider that indexes media on the shared volume. The collections are Images, Video, Audio and Downloads.1

Querying:

val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME,
    MediaStore.Images.Media.DATE_TAKEN,
)
 
contentResolver.query(
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    projection,
    null,
    null,
    "${MediaStore.Images.Media.DATE_TAKEN} DESC",
)?.use { cursor ->
    // read rows
}

The result is a set of content URIs, not file paths. That is deliberate: the URI is a capability, and the app does not need to know where the file lives.

Writing has an ownership rule. From API 29Android 10, an app can modify media it created; to modify another app's media it must request a write grant, and from API 30Android 11 the system may show a confirmation dialog.

The permissions that survived#

PermissionStatus
READ_EXTERNAL_STORAGEnarrowed to media; effectively replaced by READ_MEDIA_* on 33+
WRITE_EXTERNAL_STORAGEno longer grants broad write access; kept for compatibility
READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIOthe 33+ split permissions
READ_MEDIA_VISUAL_USER_SELECTEDselected photos, API 34Android 14
MANAGE_EXTERNAL_STORAGEall-files access, special, Play-reviewed

The split into READ_MEDIA_* is significant: an app that only needs images no longer asks for audio. Each grant is narrower, and the user sees a more specific prompt.

MANAGE_EXTERNAL_STORAGE is the exception, and it is deliberately hard to get.2 It is intended for file managers, backup tools and antivirus apps; Play policy restricts its use, and the user grants it in a special settings screen rather than a dialog.

The photo picker#

API 33Android 13 introduced the system photo picker. The app launches it, the user selects items, and the app receives URIs — with no storage permission at all.

Loading diagram…
Text description of this diagram

Three ways to get a photo in 2015, 2020 and 2024. In 2015 the app asked for READ_EXTERNAL_STORAGE and could then read any file. In 2020 it queried MediaStore and needed a grant only for files it did not create. In 2024 the system photo picker returns individual URIs, so no media permission is needed at all.

It was backported through Play services, which means it works on devices older than the API level that introduced it. That backport is one of the more consequential platform decisions in years: it let apps adopt the safer model without waiting for an OS update.

Selected photos#

API 34Android 14 added READ_MEDIA_VISUAL_USER_SELECTED: the user picks a subset of their photos and videos, and the app can access those items, and only those, across sessions.

The app must handle three states: full access, partial access, and no access. That three-state model is the new normal for media permissions, and it is why a boolean "do I have permission" check is no longer sufficient.

The external storage migration#

Apps that had years of files in the shared volume needed a path. The platform provides:

  • MediaStore.Downloads for files the user downloads.
  • App-specific external directories for files the app owns.
  • SAF tree grants for directories the user chooses.

An app migrating from legacy storage typically moves its files into its app-specific directory on first run, or adopts a SAF tree for a user-chosen folder.

What scoped storage does not fix#

It does not stop an app from reading files the user gives it. It does not prevent an app from uploading those files. And it does not remove files already written to shared storage by legacy apps. It bounds access, not use.

Why MediaStore is an index and an API at once#

MediaStore is a content provider whose database lists files, and whose openFile resolves them. The two halves can disagree: a file written directly into a media directory exists on disk but may not appear in the index until a scan runs.

That is why writing media through MediaStore rather than by path is not just a policy requirement but a correctness one: the insert registers the file, and the app receives a URI it can use immediately.

Scoped storage and file paths#

The model does not forbid paths entirely. An app can still use File APIs inside its own directories, and MediaStore returns DATA columns on some releases for compatibility.

What it forbids is assuming a path works for another app's file. The URI is the capability; the path is an implementation detail that may or may not be resolvable.

The three-state media permission#

With selected photos, a media permission has three outcomes:

StateWhat the app sees
Fullthe whole library through MediaStore
Partialonly the user-selected items
Nonenothing

An app that checks a boolean will treat partial as full and show an incomplete gallery, or as none and ask again. The correct check is on the specific permission, and the UI should reflect what the user chose.

What the picker cost#

The picker is a separate process and a separate UI. That means:

  • An extra activity transition, which is a visible latency the app cannot remove.
  • No custom filtering beyond MIME type and a few options.
  • No access to the library beyond the selection.

For an app whose core feature is browsing a photo library, the picker is insufficient and READ_MEDIA_IMAGES with a justification is the right call. For an app that needs one avatar, the picker is strictly better.

Migration in practice#

An app moving from legacy storage typically:

  1. Writes new files to its app-specific directory.
  2. Migrates existing shared-volume files into MediaStore or the app-specific directory on first run.
  3. Uses SAF for any user-chosen folder it needs to keep.

The migration must be idempotent and must handle a partially migrated state, because it can be interrupted by a process kill.

What "all files access" really means#

MANAGE_EXTERNAL_STORAGE grants read and write to nearly the whole shared volume, including other apps' media and downloads. It is the modern equivalent of the old WRITE_EXTERNAL_STORAGE, and it exists because a file manager genuinely cannot function without it.

The platform's approach is to keep the permission available but make it expensive: a special settings screen rather than a dialog, a Play policy review, and a visible declaration in the store listing. The friction is the point.

Why MediaStore is a provider and not a directory#

MediaStore is a content provider whose database is the index, and whose openFile resolves the actual bytes. The index and the bytes can be in different places, which is what lets the same API cover local files, cloud-backed files and, on some devices, files on adopted storage.

The consequence for an app is that a MediaStore URI is the only reliable handle. A path may work today and stop working after a provider change.

The photo picker's backport#

The picker arrived in API 33, but it was backported through Play services so that it works on earlier releases. That is unusual: most platform features wait for the OS update.

The backport mattered because the storage model's safety depends on apps adopting the picker, and waiting for OS adoption would have taken years.

What a well-behaved storage app looks like#

  • It uses its app-specific directories for its own data.
  • It uses MediaStore for media it creates or edits.
  • It uses the picker for one-off user selections.
  • It uses a SAF tree when it needs a folder the user chooses.
  • It asks for MANAGE_EXTERNAL_STORAGE only if its core function is file management.
  • It handles a revoked grant by falling back to the picker rather than crashing.

How it evolved#

API 1–API 28Legacybroad storage permissionAPI 1
API 29 →Opt-outrequestLegacyExternalStorageAPI 29
API 30 →Enforcedscoped for apps targeting 30API 30
API 33 →Per-itemphoto picker, then selected photos in 34API 33
Storage changes10 releases
APIVersionWhat changed
31.5 Cupcake
  • android.widget video and media improvements
123.1 Honeycomb
  • ParcelFileDescriptor improvements
154.0.3 Ice Cream Sandwich
  • New calendar and media provider APIs
194.4 KitKat
  • Storage Access Framework
247.0 Nougat
  • FileProvider and StrictMode for file URLs
2910
  • Scoped storage with requestLegacyExternalStorage opt-out
3011
  • Scoped storage enforced for targeting apps
3313 Tiramisu
  • READ_MEDIA_IMAGES/VIDEO/AUDIO replace storage reads
3414 Upside Down Cake
  • READ_MEDIA_VISUAL_USER_SELECTED
3717 Cinnamon Bun
  • ACTION_PICK_CONTACTS gives session-scoped contact field access

Under the hood#

Curiosity corner#

  • MediaStore is a provider, not a database you query directly. The index is maintained by MediaProvider, and a file written directly to the filesystem may not appear in it until a scan.
  • The write-grant dialog is a real user decision. From Android 11, modifying another app's media can require explicit confirmation, which is why photo editors must handle a denial gracefully.
  • The picker is a separate process. Your app does not see the library; it sees the result of a selection.

Try it#

Query the image collection
adb shell content query --uri content://media/external/images/media --projection _id:_display_name:mime_type
Example output
Row: 0 _id=42, _display_name=IMG_20240101_120000.jpg, mime_type=image/jpeg
Check an app's storage permissions
adb shell dumpsys package com.example.app | grep -A2 "runtime permissions"
Example output
android.permission.READ_MEDIA_IMAGES: granted=true
android.permission.READ_MEDIA_VISUAL_USER_SELECTED: granted=true
The app's external directory
adb shell ls -la /storage/emulated/0/Android/data/com.example.app/
Example output
drwxrwx--x 2 u0_a123 u0_a123 4096 ... files

Check yourself#

Check yourself

1 / 12

Why did WRITE_EXTERNAL_STORAGE become a privacy problem?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsStorage updates in Android 11developer.android.comaccessed 2026-10-27↩
  2. docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
  3. docsData and file storage overview | App data and filesdeveloper.android.comaccessed 2026-10-27↩
  4. docsBehavior changes: Apps targeting Android 11developer.android.comaccessed 2026-10-27↩
  5. docsBehavior changes: Apps targeting Android 14developer.android.comaccessed 2026-10-27↩
  6. docsStorage overviewsource.android.comaccessed 2026-10-27↩