Skip to content

Android 11 Scoped Storage

Android 11 introduced significant changes to how apps can access external storage. This article provides a comprehensive guide to understanding and implementing scoped storage in your Android applications.

Problem Statement

With Android 11 (API level 30), Google implemented scoped storage restrictions that prevent apps from directly accessing files using traditional file paths like Environment.getExternalStorageDirectory(). This change enhances user privacy by limiting app access to only the files they create or need to access for their core functionality.

Many developers find their existing file access code stops working when targeting Android 11, requiring migration to new storage access patterns.

Solutions Overview

The MediaStore API is the primary method for accessing media files in scoped storage. It provides a content provider interface for accessing images, videos, and audio files.

kotlin
// Query images from external storage
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}

val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME,
    MediaStore.Images.Media.SIZE
)

val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"

contentResolver.query(
    collection,
    projection,
    null,
    null,
    sortOrder
)?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)

    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val size = cursor.getInt(sizeColumn)
        val contentUri = ContentUris.withAppendedId(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            id
        )
        // Use the contentUri to access the file
    }
}

2. App-Specific Directories

Use app-specific directories for files that don't need to be shared with other apps:

kotlin
// Get app-specific directory for pictures
val picturesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val imageFile = File(picturesDir, "my_image.jpg")

3. Storage Access Framework (SAF)

For user-selected files, use the Storage Access Framework:

kotlin
// Request to create a document
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf" // MIME type
        putExtra(Intent.EXTRA_TITLE, "document.pdf")
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

Permission Handling

Standard Permissions

For most use cases, you only need these permissions:

xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

WARNING

WRITE_EXTERNAL_STORAGE has no effect on Android 10 (API 29) and higher. Use android:maxSdkVersion="28" to only request it on older devices.

MANAGE_EXTERNAL_STORAGE (Use With Caution)

The MANAGE_EXTERNAL_STORAGE permission grants broad access to external storage but has significant limitations:

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

Play Store Restrictions

Apps requesting MANAGE_EXTERNAL_STORAGE are subject to strict review on Google Play Store and are typically only approved for file managers, backup tools, or antivirus applications. Most apps should avoid this permission.

Permission Request Logic

kotlin
private fun checkStoragePermission(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        Environment.isExternalStorageManager()
    } else {
        val readResult = ContextCompat.checkSelfPermission(
            this, 
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
        val writeResult = ContextCompat.checkSelfPermission(
            this, 
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
        readResult == PackageManager.PERMISSION_GRANTED && 
        writeResult == PackageManager.PERMISSION_GRANTED
    }
}

private fun requestStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        try {
            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
            intent.data = Uri.parse("package:${packageName}")
            startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE)
        } catch (e: Exception) {
            val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
            startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE)
        }
    } else {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ),
            REQUEST_CODE_STORAGE
        )
    }
}

File Access Patterns

Accessing Non-Media Files

On Android 11+, non-media files (txt, pdf, csv, etc.) can be accessed if:

  • Files are in app-specific directories or private directories
  • Files are in Shared folders (/Documents or /Downloads) and were created by your app
  • Using Storage Access Framework (SAF)

Accessing Media Files

Media files (jpg, png, mp3, mp4, etc.) have different access rules:

Without READ_EXTERNAL_STORAGE permission, you can access:

  • Media files in Shared folders created by your app
  • Files in app-specific directories

With READ_EXTERNAL_STORAGE permission, you can access:

  • All media files from all areas of external storage

Migration Strategy

1. Update AndroidManifest.xml

xml
<application
    android:requestLegacyExternalStorage="true"
    tools:targetApi="q">
    <!-- For Android 11+ compatibility -->
    <queries>
        <intent>
            <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
        <!-- Add other intents your app uses -->
    </queries>
    
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>

2. Create file_paths.xml

xml
<!-- res/xml/file_paths.xml -->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path
        name="internal_images"
        path="Pictures" />
    <external-files-path
        name="internal_documents"
        path="Documents" />
</paths>

3. Replace File Path Usage

Replace deprecated code:

kotlin
// Deprecated approach
val file = File(Environment.getExternalStorageDirectory(), "myfile.txt")

// Modern approach
val file = File(context.getExternalFilesDir(null), "myfile.txt")
// OR use MediaStore for shared files

Best Practices

  1. Use app-specific directories for files that don't need to be shared
  2. Use MediaStore API for accessing shared media files
  3. Use Storage Access Framework for user-selected files
  4. Avoid MANAGE_EXTERNAL_STORAGE unless absolutely necessary
  5. Test on multiple API levels to ensure compatibility

TIP

For complex file operations, consider using libraries like SimpleStorage that simplify scoped storage implementation.

Common Pitfalls

  1. Assuming file path access works - Always use ContentResolver with URIs
  2. Not handling permission changes between Android versions
  3. Forgetting to declare <queries> in AndroidManifest for intents
  4. Not testing on actual Android 11+ devices

By following these guidelines and migrating to the recommended APIs, your app will be compatible with Android 11's scoped storage while maintaining user privacy and meeting Play Store requirements.