Android Storage Permission Adaptation Guide for Android 11+

When I tried to implement a very simple requirement — download an image and save it to local storage — everything looked fine at first.

  • Honor (Android 10) – works
  • Redmi (Android 11) – works
  • Xiaomi (Android 13) – works
  • Samsung (Android 13) – completely failed: the storage permission dialog would never show up

Same code, same feature, but one device on Android 13 simply refused to show the permission prompt. That’s how this small “download image” task turned into a deep dive into Scoped Storage and MANAGE_EXTERNAL_STORAGE.

This post summarizes how I adapted storage permissions for Android 11 and above, and how I handle the different behaviors across versions.


1. The Bug: Storage Permission Dialog Never Shows (Samsung Android 13)

The requirement is straightforward:

Download an image and save it to the device so it shows up in the gallery.

On my three test devices:

  • Honor – Android 10 → OK
  • Redmi – Android 11 → OK
  • Xiaomi – Android 13 → OK

But on a Samsung device running Android 13, the system never showed the storage permission dialog, no matter how I requested it.

At first I thought this was just another OEM quirk, but after checking the storage permission changes across Android versions, I realized I was relying on behavior that had effectively been deprecated on Android 13 when targeting SDK 33.


2. Root Cause: WRITE/READ_EXTERNAL_STORAGE Are Deprecated on Android 13 (SDK 33)

In older Android versions, we could simply declare these two permissions in the manifest:

  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE

and then request them at runtime when needed.

On Android 13 (SDK 33) with targetSdkVersion = 33, that approach starts to break:

  • WRITE_EXTERNAL_STORAGE is deprecated and effectively useless on recent Android versions
  • If you add maxSdkVersion=32 to these permissions, they still work on Android 11/12
    but they are ignored on Android 13 when targeting 33
  • At the same time, Play Store requires new apps to target at least SDK 33

So for Android 11+ we must adapt to:

  • Scoped Storage
  • And in some cases, the special permission: MANAGE_EXTERNAL_STORAGE

MANAGE_EXTERNAL_STORAGE grants an app broad access to all shared storage content (including non-media files). It does not allow access to other apps’ private directories, but it’s still considered a highly sensitive permission by Google Play.

To support different Android versions, I ended up splitting permission handling into:

  • Before Android 11 (API < 30) – old-style external storage permissions
  • Android 11 and above – Scoped Storage + special handling with MANAGE_EXTERNAL_STORAGE where strictly needed

3. Step-by-Step Adaptation

3.1 Declare MANAGE_EXTERNAL_STORAGE in the Manifest

In the AndroidManifest.xml:

<uses-permission
    android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" />

⚠️ Note: Because MANAGE_EXTERNAL_STORAGE is a sensitive permission, it’s restricted on Google Play. I’ll talk about alternatives later if you just want to save images.


3.2 Check Whether Permission Is Granted

I used EasyPermissions to simplify permission checks.

private fun checkPer(activity: PreViewActivity): Boolean {
    return if (Build.VERSION.SDK_INT >= 30) {
        EasyPermissions.hasPermissions(
            activity,
            android.Manifest.permission.MANAGE_EXTERNAL_STORAGE
        )
    } else {
        EasyPermissions.hasPermissions(
            activity,
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
    }
}
  • On Android 11+ (API >= 30): I check MANAGE_EXTERNAL_STORAGE
  • On Android 10 and below: I still check WRITE_EXTERNAL_STORAGE

This split is crucial, because WRITE_EXTERNAL_STORAGE no longer behaves the way it did on newer versions.


3.3 Request Permission When It’s Missing

If the permission is not granted, I request it differently based on the system version.

private fun requestStoragePermission(activity: PreViewActivity, curImg: Int) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android 11+ – redirect to the system "All files access" settings page
        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
        intent.data = Uri.parse("package:" + activity.packageName)
        activity.startActivityForResult(intent, 200)
    } else {
        // Android 10 and below – normal runtime permission
        val perm = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
        PaperThreeVariable.isToRequestPer = true
        EasyPermissions.requestPermissions(
            PermissionRequest.Builder(
                activity,
                200,
                perm
            ).build()
        )
    }
}
  • On Android 11+: you can’t just “pop” a normal runtime dialog for MANAGE_EXTERNAL_STORAGE
    You must send the user to the system settings page where they manually grant “All files access”.
  • On Android 10 and below: classic runtime permission dialog still works.

3.4 Handle Permission Callbacks

EasyPermissions helps bridge the Activity’s callback and our own logic:

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}

override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) {
    AppInitUtils().saveFreshAppImageToGallery(this, curImg)
    PaperThreeVariable.isToRequestPer = false
}

override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
    PaperThreeVariable.isToRequestPer = false
    if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
        AppSettingsDialog.Builder(this)
            .setRationale("This function requires storage permission to be enabled")
            .setNegativeButton("No")
            .setPositiveButton("Yes")
            .build()
            .show()
    }
}

Why I use EasyPermissions here:

  • Users can permanently deny permissions, which makes repeated requests fail silently
  • EasyPermissions makes it easier to:
    • Detect “permanently denied” state
    • Show a dialog that guides users to system settings → app permissions to enable storage access manually

Once the permission is granted, I call:

AppInitUtils().saveFreshAppImageToGallery(this, curImg)

to actually save the image and refresh the gallery.

After this adaptation, the Samsung Android 13 device finally behaved the same as the others.

Side note: My Xiaomi device reported as Android 13 but Android Studio’s “historical connected devices” recognized it as Android 12. That might explain why it still worked in some cases — but this is exactly why version-aware permission handling is important.


4. About MANAGE_EXTERNAL_STORAGE and Google Play Restrictions

MANAGE_EXTERNAL_STORAGE is powerful:

It grants read/write access to all shared storage on the device.

Because of this, Google Play treats it as a highly sensitive permission:

  • It’s mainly intended for file manager / backup / antivirus type apps
  • You have to submit a justification to use it
  • If your app is only a typical consumer app (e.g., saving images, simple downloads), your request will most likely be rejected

So if your only requirement is:

“Save an image to the gallery and make it visible to the user.”

then you should strongly consider avoiding MANAGE_EXTERNAL_STORAGE and instead:

  • Use MediaStore to insert images into the system media library
  • Or use APIs that can save images without requiring full file access

There are several patterns to:

  • Save an image to the Pictures/DCIM directory
  • Notify the media scanner or rely on MediaStore so the gallery can pick it up
  • Do all this without requesting MANAGE_EXTERNAL_STORAGE

For internal or non-Play Store distribution (e.g., enterprise internal app stores), you technically can still use Environment.getExternalStorageDirectory(), but I don’t recommend designing a new app around this in 2025.


5. Version-by-Version Summary (Android 9 → 13)

To put everything in one place, here’s a high-level summary of how external storage and permissions behave across versions.

Android 9 and Below (API 28 and earlier)

  • Permissions:
    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE
  • Behavior:
    • Apps can freely access /sdcard and its subdirectories
    • Files created by the app remain on the device even after the app is uninstalled
  • Typical approach:
    • Directly read/write under external storage paths

Android 10 (API 29) – Scoped Storage Introduced

  • Permissions:
    • READ_EXTERNAL_STORAGE still works
    • WRITE_EXTERNAL_STORAGE still exists, but its effective scope is reduced
  • Behavior:
    • Scoped Storage is introduced:
      • Apps are limited to their own app-specific directory under
        Android/data/your.package.name/
      • Direct access to other apps’ files is restricted
    • Media files (images, videos, audio) should be accessed via MediaStore
    • requestLegacyExternalStorage=true could temporarily keep old behavior
      (but this flag is ignored starting from Android 11)
  • Recommended approach:
    • For images/videos/audio: use MediaStore
    • For private files: use getExternalFilesDir() or getDataDir()

Android 11 (API 30) – Scoped Storage Enforced

  • Permissions:
    • READ_EXTERNAL_STORAGE works, but only for media managed by MediaStore
    • WRITE_EXTERNAL_STORAGE is effectively obsolete for general external storage
    • MANAGE_EXTERNAL_STORAGE introduced for special “all files access” use cases
  • Behavior:
    • requestLegacyExternalStorage=true no longer works; Scoped Storage is always on
    • Access to /sdcard/ root is blocked
    • Apps can only:
      • Access their own private directories
      • Access shared media via MediaStore
  • Recommended approach:
    • For typical apps:
      • Use MediaStore or SAF (ACTION_OPEN_DOCUMENT, ACTION_CREATE_DOCUMENT) for user-selected files
    • Only consider MANAGE_EXTERNAL_STORAGE if your app is genuinely a file manager, backup tool, security app, etc.

Android 13 (API 33) – Media Permissions Split

  • Permissions:
    • READ_MEDIA_IMAGES – access images
    • READ_MEDIA_VIDEO – access videos
    • READ_MEDIA_AUDIO – access audio
  • Behavior:
    • Media permissions are fine-grained:
      • Users can grant only image access, only video access, etc.
    • Scoped Storage rules from Android 11 remain in place
  • Recommended approach:
    • Request the specific media permissions you need:
      • For example, if you only work with images, request only READ_MEDIA_IMAGES
    • Do not request READ_EXTERNAL_STORAGE on Android 13+, as it’s replaced by the new media permissions

Quick Matrix (Conceptual)

  • Android 9 and below
    • Access to external storage is broad, controlled by READ/WRITE_EXTERNAL_STORAGE
  • Android 10
    • Scoped Storage introduced, but there are escape hatches (requestLegacyExternalStorage)
  • Android 11
    • Scoped Storage enforced, legacy switches are removed
    • MANAGE_EXTERNAL_STORAGE appears but is highly restricted
  • Android 13
    • Media access split into READ_MEDIA_* permissions
    • Same Scoped Storage rules, but more fine-grained user control

6. Takeaways

  • Don’t assume “it works on one Android 13 device” means it works everywhere; OEMs and system reports can be inconsistent.
  • For Android 11+, think in terms of:
    • App private directories + MediaStore + SAF, not “raw /sdcard access”
  • Treat MANAGE_EXTERNAL_STORAGE as a last resort for very specific app types, especially if you plan to publish on Google Play.
  • Always test on multiple devices and Android versions, especially when it comes to permissions and storage.

This article comes from my own debugging and adaptation in real Android projects (including a Samsung device on Android 13). GPT only helped translate and polish the wording; all technical content and decisions are mine.

About The Author

Share Post:

Summarize with AI​

Table of Contents

Stay Connected

More Updates