Android 13 External Storage Permissions
Android 13 introduced significant changes to how apps access files on external storage. This article clarifies the new permission model and provides practical solutions for handling different file types.
Problem Statement
Android 13 (API level 33) replaces the broad READ_EXTERNAL_STORAGE
and WRITE_EXTERNAL_STORAGE
permissions with granular media-specific permissions:
READ_MEDIA_IMAGES
for images and photosREAD_MEDIA_VIDEO
for videosREAD_MEDIA_AUDIO
for audio files
This change creates confusion for developers who need to access non-media files like PDFs, documents, or other file types that don't fit these categories.
Understanding the New Permission Model
Key Changes in Android 13
- Media-specific permissions replace the general storage permissions
- No new permission exists for documents (PDFs, Excel files, etc.)
- The changes only affect apps targeting Android 13 (targetSdkVersion 33+)
- Apps can still access their own files without permissions
For accessing files that aren't images, videos, or audio, the approach depends on your specific use case and Android version.
Solution 1: Using the Storage Access Framework
For most file access scenarios, Google recommends using the Storage Access Framework rather than direct storage permissions:
// Open a specific document
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf" // Specify your file type
}
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT)
// Or open a directory tree for broader access
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE_OPEN_TREE)
This approach doesn't require any storage permissions and works across all Android versions.
Solution 2: Conditional Permission Handling
For apps that need direct file system access, implement conditional permission handling based on the Android version:
<!-- For Android 10 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- For Android 11+ if you need all files access -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Optional: Request legacy storage for Android 10 -->
<application
android:requestLegacyExternalStorage="true"
... />
fun checkStoragePermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11+ - Use MANAGE_EXTERNAL_STORAGE for full access
if (!Environment.isExternalStorageManager()) {
val uri = Uri.parse("package:${applicationContext.packageName}")
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
startActivity(intent)
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10 - Use scoped storage or requestLegacyExternalStorage
// No permissions needed for app-owned files
} else {
// Android 9 and below - Request traditional permissions
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_CODE_STORAGE_PERMISSION)
}
}
}
MANAGE_EXTERNAL_STORAGE Restrictions
The MANAGE_EXTERNAL_STORAGE
permission is considered a high-risk permission. Google Play requires a declaration form for apps using this permission, and it may be restricted for certain app categories.
Solution 3: Media Store for Common File Types
For accessing common file types through the MediaStore API:
// Query for PDF files
val contentResolver = context.contentResolver
val projection = arrayOf(MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME)
val selection = "${MediaStore.Files.FileColumns.MIME_TYPE} = ?"
val selectionArgs = arrayOf("application/pdf")
val sortOrder = "${MediaStore.Files.FileColumns.DATE_ADDED} DESC"
val cursor = contentResolver.query(
MediaStore.Files.getContentUri("external"),
projection,
selection,
selectionArgs,
sortOrder
)
// Process the results
cursor?.use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
val name = it.getString(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
val contentUri = ContentUris.withAppendedId(
MediaStore.Files.getContentUri("external"), id
)
// Use the contentUri to access the file
}
}
Best Practices
- Use the Storage Access Framework when possible - it's the most user-friendly approach
- Request minimal permissions - only ask for what you absolutely need
- Handle permissions conditionally based on Android version
- Use MediaStore for media files - it provides structured access to shared media
- Consider using app-specific storage for files that don't need to be shared
No Permissions Needed for App-Owned Files
On Android 10+, you don't need storage permissions to access files that your app created. This includes files in your app-specific directory and media files your app added to the MediaStore.
Version-Specific Considerations
Android Version | Recommended Approach |
---|---|
≤ Android 9 | Use READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE |
Android 10 | Use scoped storage or requestLegacyExternalStorage="true" |
Android 11+ | Use granular media permissions or Storage Access Framework |
Android 13+ | Required to use granular permissions for media files |
Conclusion
While Android 13's granular media permissions initially seem restrictive, they actually provide more precise control over file access. For non-media files, the Storage Access Framework offers a robust solution without requiring broad storage permissions. Always prefer the least privileged approach that meets your app's needs, and implement conditional logic to handle different Android versions appropriately.