{"id":3803,"date":"2025-11-27T18:25:57","date_gmt":"2025-11-27T10:25:57","guid":{"rendered":"https:\/\/imastudio.com\/?p=3803"},"modified":"2025-11-27T18:25:58","modified_gmt":"2025-11-27T10:25:58","slug":"android-storage-permission-adaptation-guide","status":"publish","type":"post","link":"https:\/\/imastudio.com\/pt\/blog\/android-storage-permission-adaptation-guide","title":{"rendered":"Android Storage Permission Adaptation Guide for Android 11+"},"content":{"rendered":"<p>When I tried to implement a very simple requirement \u2014 <strong>download an image and save it to local storage<\/strong> \u2014 everything looked fine at first.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Honor (Android 10) \u2013 works<\/li>\n\n\n\n<li>Redmi (Android 11) \u2013 works<\/li>\n\n\n\n<li>Xiaomi (Android 13) \u2013 works<\/li>\n\n\n\n<li><strong>Samsung (Android 13)<\/strong> \u2013 completely failed: the storage permission dialog would never show up<\/li>\n<\/ul>\n\n\n\n<p>Same code, same feature, but one device on Android 13 simply refused to show the permission prompt. That\u2019s how this small \u201cdownload image\u201d task turned into a deep dive into <strong>Scoped Storage<\/strong> e <strong>MANAGE_EXTERNAL_STORAGE<\/strong>.<\/p>\n\n\n\n<p>This post summarizes how I adapted storage permissions for <strong>Android 11 and above<\/strong>, and how I handle the different behaviors across versions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. The Bug: Storage Permission Dialog Never Shows (Samsung Android 13)<\/h2>\n\n\n\n<p>The requirement is straightforward:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Download an image and save it to the device so it shows up in the gallery.<\/p>\n<\/blockquote>\n\n\n\n<p>On my three test devices:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Honor \u2013 Android 10 \u2192 OK<\/li>\n\n\n\n<li>Redmi \u2013 Android 11 \u2192 OK<\/li>\n\n\n\n<li>Xiaomi \u2013 Android 13 \u2192 OK<\/li>\n<\/ul>\n\n\n\n<p>But on a <strong>Samsung device running Android 13<\/strong>, the system <strong>never showed the storage permission dialog<\/strong>, no matter how I requested it.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"184\" src=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134-1024x184.jpeg\" alt=\"\" class=\"wp-image-3810\" srcset=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134-1024x184.jpeg 1024w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134-300x54.jpeg 300w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134-768x138.jpeg 768w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134-18x3.jpeg 18w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182134.jpeg 1277w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"168\" src=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-1024x168.jpeg\" alt=\"\" class=\"wp-image-3811\" srcset=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-1024x168.jpeg 1024w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-300x49.jpeg 300w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-768x126.jpeg 768w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-1536x253.jpeg 1536w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139-18x3.jpeg 18w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182139.jpeg 1806w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"174\" src=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-1024x174.jpeg\" alt=\"\" class=\"wp-image-3808\" srcset=\"https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-1024x174.jpeg 1024w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-300x51.jpeg 300w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-768x131.jpeg 768w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-1536x262.jpeg 1536w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150-18x3.jpeg 18w, https:\/\/imastudio.com\/wp-content\/uploads\/2025\/11\/20251127-182150.jpeg 1879w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Root Cause: WRITE\/READ_EXTERNAL_STORAGE Are Deprecated on Android 13 (SDK 33)<\/h2>\n\n\n\n<p>In older Android versions, we could simply declare these two permissions in the manifest:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>READ_EXTERNAL_STORAGE<\/code><\/li>\n\n\n\n<li><code>WRITE_EXTERNAL_STORAGE<\/code><\/li>\n<\/ul>\n\n\n\n<p>and then request them at runtime when needed.<\/p>\n\n\n\n<p>On Android 13 (SDK 33) with <strong>targetSdkVersion = 33<\/strong>, that approach starts to break:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>WRITE_EXTERNAL_STORAGE<\/code> is <strong>deprecated and effectively useless<\/strong> on recent Android versions<\/li>\n\n\n\n<li>If you add <code>maxSdkVersion=32<\/code> to these permissions, they still work on Android 11\/12<br>but they are <strong>ignored<\/strong> on Android 13 when targeting 33<\/li>\n\n\n\n<li>At the same time, Play Store requires new apps to target at least SDK 33<\/li>\n<\/ul>\n\n\n\n<p>So for Android 11+ we must adapt to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Scoped Storage<\/strong><\/li>\n\n\n\n<li>And in some cases, the special permission: <code>MANAGE_EXTERNAL_STORAGE<\/code><\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>MANAGE_EXTERNAL_STORAGE<\/strong> grants an app broad access to all shared storage content (including non-media files). It does <strong>n\u00e3o<\/strong> allow access to other apps\u2019 private directories, but it\u2019s still considered a highly sensitive permission by Google Play.<\/p>\n<\/blockquote>\n\n\n\n<p>To support different Android versions, I ended up splitting permission handling into:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Before Android 11 (API &lt; 30)<\/strong> \u2013 old-style external storage permissions<\/li>\n\n\n\n<li><strong>Android 11 and above<\/strong> \u2013 Scoped Storage + special handling with <code>MANAGE_EXTERNAL_STORAGE<\/code> where strictly needed<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Step-by-Step Adaptation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 Declare MANAGE_EXTERNAL_STORAGE in the Manifest<\/h3>\n\n\n\n<p>In the <code>AndroidManifest.xml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;uses-permission\n    android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"\n    tools:ignore=\"ScopedStorage\" \/><\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u26a0\ufe0f Note: Because <code>MANAGE_EXTERNAL_STORAGE<\/code> is a sensitive permission, it\u2019s restricted on Google Play. I\u2019ll talk about alternatives later if you just want to save images.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3.2 Check Whether Permission Is Granted<\/h3>\n\n\n\n<p>I used <strong>EasyPermissions<\/strong> to simplify permission checks.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private fun checkPer(activity: PreViewActivity): Boolean {\n    return if (Build.VERSION.SDK_INT &gt;= 30) {\n        EasyPermissions.hasPermissions(\n            activity,\n            android.Manifest.permission.MANAGE_EXTERNAL_STORAGE\n        )\n    } else {\n        EasyPermissions.hasPermissions(\n            activity,\n            android.Manifest.permission.WRITE_EXTERNAL_STORAGE\n        )\n    }\n}\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On <strong>Android 11+ (API >= 30)<\/strong>: I check <code>MANAGE_EXTERNAL_STORAGE<\/code><\/li>\n\n\n\n<li>On <strong>Android 10 and below<\/strong>: I still check <code>WRITE_EXTERNAL_STORAGE<\/code><\/li>\n<\/ul>\n\n\n\n<p>This split is crucial, because <code>WRITE_EXTERNAL_STORAGE<\/code> no longer behaves the way it did on newer versions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3.3 Request Permission When It\u2019s Missing<\/h3>\n\n\n\n<p>If the permission is not granted, I request it differently based on the system version.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private fun requestStoragePermission(activity: PreViewActivity, curImg: Int) {\n    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.R) {\n        \/\/ Android 11+ \u2013 redirect to the system \"All files access\" settings page\n        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)\n        intent.data = Uri.parse(\"package:\" + activity.packageName)\n        activity.startActivityForResult(intent, 200)\n    } else {\n        \/\/ Android 10 and below \u2013 normal runtime permission\n        val perm = android.Manifest.permission.WRITE_EXTERNAL_STORAGE\n        PaperThreeVariable.isToRequestPer = true\n        EasyPermissions.requestPermissions(\n            PermissionRequest.Builder(\n                activity,\n                200,\n                perm\n            ).build()\n        )\n    }\n}\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On <strong>Android 11+<\/strong>: you can\u2019t just \u201cpop\u201d a normal runtime dialog for <code>MANAGE_EXTERNAL_STORAGE<\/code><br>You must send the user to the system settings page where they manually grant \u201cAll files access\u201d.<\/li>\n\n\n\n<li>On <strong>Android 10 and below<\/strong>: classic runtime permission dialog still works.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3.4 Handle Permission Callbacks<\/h3>\n\n\n\n<p>EasyPermissions helps bridge the Activity\u2019s callback and our own logic:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override fun onRequestPermissionsResult(\n    requestCode: Int,\n    permissions: Array&lt;String&gt;,\n    grantResults: IntArray\n) {\n    super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)\n}\n\noverride fun onPermissionsGranted(requestCode: Int, perms: MutableList&lt;String&gt;) {\n    AppInitUtils().saveFreshAppImageToGallery(this, curImg)\n    PaperThreeVariable.isToRequestPer = false\n}\n\noverride fun onPermissionsDenied(requestCode: Int, perms: MutableList&lt;String&gt;) {\n    PaperThreeVariable.isToRequestPer = false\n    if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {\n        AppSettingsDialog.Builder(this)\n            .setRationale(\"This function requires storage permission to be enabled\")\n            .setNegativeButton(\"No\")\n            .setPositiveButton(\"Yes\")\n            .build()\n            .show()\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Why I use EasyPermissions here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Users can <strong>permanently deny<\/strong> permissions, which makes repeated requests fail silently<\/li>\n\n\n\n<li>EasyPermissions makes it easier to:\n<ul class=\"wp-block-list\">\n<li>Detect \u201cpermanently denied\u201d state<\/li>\n\n\n\n<li>Show a dialog that guides users to <strong>system settings \u2192 app permissions<\/strong> to enable storage access manually<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Once the permission is granted, I call:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>AppInitUtils().saveFreshAppImageToGallery(this, curImg)\n<\/code><\/pre>\n\n\n\n<p>to actually save the image and refresh the gallery.<\/p>\n\n\n\n<p>After this adaptation, the Samsung Android 13 device finally behaved the same as the others.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Side note: My Xiaomi device reported as Android 13 but Android Studio\u2019s \u201chistorical connected devices\u201d recognized it as Android 12. That might explain why it still worked in some cases \u2014 but this is exactly why version-aware permission handling is important.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. About MANAGE_EXTERNAL_STORAGE and Google Play Restrictions<\/h2>\n\n\n\n<p><code>MANAGE_EXTERNAL_STORAGE<\/code> is powerful:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>It grants read\/write access to <strong>all shared storage<\/strong> on the device.<\/p>\n<\/blockquote>\n\n\n\n<p>Because of this, Google Play treats it as a <strong>highly sensitive permission<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It\u2019s mainly intended for <strong>file manager \/ backup \/ antivirus<\/strong> type apps<\/li>\n\n\n\n<li>You have to submit a justification to use it<\/li>\n\n\n\n<li>If your app is only a typical consumer app (e.g., saving images, simple downloads), your request will most likely be <strong>rejected<\/strong><\/li>\n<\/ul>\n\n\n\n<p>So if your only requirement is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u201cSave an image to the gallery and make it visible to the user.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>then you should <strong>strongly consider avoiding<\/strong> <code>MANAGE_EXTERNAL_STORAGE<\/code> and instead:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>MediaStore<\/strong> to insert images into the system media library<\/li>\n\n\n\n<li>Or use APIs that can save images without requiring full file access<\/li>\n<\/ul>\n\n\n\n<p>There are several patterns to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Save an image to the Pictures\/DCIM directory<\/li>\n\n\n\n<li>Notify the media scanner or rely on MediaStore so the gallery can pick it up<\/li>\n\n\n\n<li>Do all this <strong>without requesting MANAGE_EXTERNAL_STORAGE<\/strong><\/li>\n<\/ul>\n\n\n\n<p>For internal or non-Play Store distribution (e.g., enterprise internal app stores), you technically can still use <code>Environment.getExternalStorageDirectory()<\/code>, but I don\u2019t recommend designing a new app around this in 2025.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Version-by-Version Summary (Android 9 \u2192 13)<\/h2>\n\n\n\n<p>To put everything in one place, here\u2019s a high-level summary of how external storage and permissions behave across versions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Android 9 and Below (API 28 and earlier)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Permissions<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>READ_EXTERNAL_STORAGE<\/code><\/li>\n\n\n\n<li><code>WRITE_EXTERNAL_STORAGE<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Behavior<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Apps can freely access <code>\/sdcard<\/code> and its subdirectories<\/li>\n\n\n\n<li>Files created by the app remain on the device even after the app is uninstalled<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Typical approach<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Directly read\/write under external storage paths<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Android 10 (API 29) \u2013 Scoped Storage Introduced<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Permissions<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>READ_EXTERNAL_STORAGE<\/code> still works<\/li>\n\n\n\n<li><code>WRITE_EXTERNAL_STORAGE<\/code> still exists, but its effective scope is reduced<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Behavior<\/strong>:\n<ul class=\"wp-block-list\">\n<li><strong>Scoped Storage<\/strong> is introduced:\n<ul class=\"wp-block-list\">\n<li>Apps are limited to their own app-specific directory under<br><code>Android\/data\/your.package.name\/<\/code><\/li>\n\n\n\n<li>Direct access to other apps\u2019 files is restricted<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Media files (images, videos, audio) should be accessed via <strong>MediaStore<\/strong><\/li>\n\n\n\n<li><code>requestLegacyExternalStorage=true<\/code> could temporarily keep old behavior<br>(but this flag is ignored starting from Android 11)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Recommended approach<\/strong>:\n<ul class=\"wp-block-list\">\n<li>For images\/videos\/audio: use <strong>MediaStore<\/strong><\/li>\n\n\n\n<li>For private files: use <code>getExternalFilesDir()<\/code> ou <code>getDataDir()<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Android 11 (API 30) \u2013 Scoped Storage Enforced<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Permissions<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>READ_EXTERNAL_STORAGE<\/code> works, but only for media managed by MediaStore<\/li>\n\n\n\n<li><code>WRITE_EXTERNAL_STORAGE<\/code> is effectively <strong>obsolete<\/strong> for general external storage<\/li>\n\n\n\n<li><code>MANAGE_EXTERNAL_STORAGE<\/code> introduced for special \u201call files access\u201d use cases<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Behavior<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>requestLegacyExternalStorage=true<\/code> no longer works; Scoped Storage is <strong>always on<\/strong><\/li>\n\n\n\n<li>Access to <code>\/sdcard\/<\/code> root is blocked<\/li>\n\n\n\n<li>Apps can only:\n<ul class=\"wp-block-list\">\n<li>Access their own private directories<\/li>\n\n\n\n<li>Access shared media via <strong>MediaStore<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Recommended approach<\/strong>:\n<ul class=\"wp-block-list\">\n<li>For typical apps:\n<ul class=\"wp-block-list\">\n<li>Use MediaStore or <strong>SAF<\/strong> (<code>ACTION_OPEN_DOCUMENT<\/code>, <code>ACTION_CREATE_DOCUMENT<\/code>) for user-selected files<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Only consider <code>MANAGE_EXTERNAL_STORAGE<\/code> if your app is genuinely a file manager, backup tool, security app, etc.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Android 13 (API 33) \u2013 Media Permissions Split<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Permissions<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>READ_MEDIA_IMAGES<\/code> \u2013 access images<\/li>\n\n\n\n<li><code>READ_MEDIA_VIDEO<\/code> \u2013 access videos<\/li>\n\n\n\n<li><code>READ_MEDIA_AUDIO<\/code> \u2013 access audio<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Behavior<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Media permissions are <strong>fine-grained<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Users can grant only image access, only video access, etc.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Scoped Storage rules from Android 11 remain in place<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Recommended approach<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Request the specific media permissions you need:\n<ul class=\"wp-block-list\">\n<li>For example, if you only work with images, request only <code>READ_MEDIA_IMAGES<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Do <strong>n\u00e3o<\/strong> request <code>READ_EXTERNAL_STORAGE<\/code> on Android 13+, as it\u2019s replaced by the new media permissions<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Quick Matrix (Conceptual)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Android 9 and below<\/strong>\n<ul class=\"wp-block-list\">\n<li>Access to external storage is broad, controlled by READ\/WRITE_EXTERNAL_STORAGE<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Android 10<\/strong>\n<ul class=\"wp-block-list\">\n<li>Scoped Storage introduced, but there are escape hatches (<code>requestLegacyExternalStorage<\/code>)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Android 11<\/strong>\n<ul class=\"wp-block-list\">\n<li>Scoped Storage enforced, legacy switches are removed<\/li>\n\n\n\n<li><code>MANAGE_EXTERNAL_STORAGE<\/code> appears but is highly restricted<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Android 13<\/strong>\n<ul class=\"wp-block-list\">\n<li>Media access split into <code>READ_MEDIA_*<\/code> permissions<\/li>\n\n\n\n<li>Same Scoped Storage rules, but more fine-grained user control<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Takeaways<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Don\u2019t assume \u201cit works on one Android 13 device\u201d means it works everywhere; OEMs and system reports can be inconsistent.<\/li>\n\n\n\n<li>For <strong>Android 11+<\/strong>, think in terms of:\n<ul class=\"wp-block-list\">\n<li><strong>App private directories + MediaStore + SAF<\/strong>, not \u201craw <code>\/sdcard<\/code> access\u201d<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Treat <code>MANAGE_EXTERNAL_STORAGE<\/code> as a <strong>last resort<\/strong> for very specific app types, especially if you plan to publish on Google Play.<\/li>\n\n\n\n<li>Always test on <strong>multiple devices and Android versions<\/strong>, especially when it comes to permissions and storage.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>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.<\/p>\n\n\n\n<p><\/p>\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>When I tried to implement a very simple requirement \u2014 download an image and save it to local storage \u2014 everything looked fine at first. Same code, same feature, but one device on Android 13 simply refused to show the permission prompt. That\u2019s how this small \u201cdownload image\u201d task turned into a deep dive into [&hellip;]<\/p>","protected":false},"author":10,"featured_media":3804,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"","footnotes":""},"categories":[35],"tags":[],"class_list":["post-3803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-knowledge-hub"],"_links":{"self":[{"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/posts\/3803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/comments?post=3803"}],"version-history":[{"count":4,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/posts\/3803\/revisions"}],"predecessor-version":[{"id":3812,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/posts\/3803\/revisions\/3812"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/media\/3804"}],"wp:attachment":[{"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/media?parent=3803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/categories?post=3803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/imastudio.com\/pt\/wp-json\/wp\/v2\/tags?post=3803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}