Mobile Development 17 min read

Understanding Android Scoped Storage and Migration Strategies

This article explains the concept and core principles of Android Scoped Storage, compares traditional file‑path, MediaStore and SAF access methods, and provides detailed migration solutions for media and non‑media files, compatibility modes, and the special MANAGE_EXTERNAL_STORAGE permission.

Snowball Engineer Team
Snowball Engineer Team
Snowball Engineer Team
Understanding Android Scoped Storage and Migration Strategies

Background – Android 10 introduced Scoped Storage to give users better control over their files, restricting how apps access external storage and creating new adaptation challenges.

What is Scoped Storage? Scoped Storage (also called Scoped Storage) separates external files into media and non‑media categories, each with different access rules, and aims to let users manage their files more cleanly.

Android Storage Areas – Android devices have three storage zones: external, internal, and system. External storage is further divided into private and public spaces.

Android Storage Mechanisms

Three common ways to access files:

File‑path access (direct I/O streams) – used before Android 10.

MediaStore – a media‑library API for indexed media files.

Storage Access Framework (SAF) – a unified UI for browsing and opening documents without needing permissions.

Using MediaStore

fun savePicture(context: Context, file: File?) {
    file?.let {
        val values = ContentValues()
        val bitmap = BitmapFactory.decodeFile(file.absolutePath)
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = context.contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
                outputStream.close()
            }
        }
    }
}

MediaStore does not require extra permissions for files created by the app, but reading other apps' shared files needs read/write permissions.

Using SAF

// Open system file picker
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// intent.setType("image/*"); // optional filter
startActivityForResult(intent, FILE_CODE);
private final String[] PROJECTION = { MediaStore.Images.Media.DISPLAY_NAME };
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Cursor cursor = this.getContentResolver()
                .query(uri, PROJECTION, null, null, null, null);
        }
    }
}

Scoped Storage Migration Solutions

Media files should be stored in app‑private or shared media directories and managed via MediaStore. Example for taking a photo and saving it:

public static File createTmpFile(Context context) {
    // Use media directory
    dir = context.getExternalFilesDir(DIRECTORY_PICTURES);
    return File.createTempFile(JPEG_FILE_PREFIX, JPEG_FILE_SUFFIX, dir);
}
// Open camera and save picture
private fun openCamera() {
    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    if (intent.resolveActivity(requireActivity().packageManager) != null) {
        mCameraFile = createTmpFile(activity)
        if (mCameraFile != null && mCameraFile!!.exists()) {
            val cameraFileUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".fileprovider", mCameraFile!!)
            } else {
                Uri.fromFile(mCameraFile)
            }
            intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraFileUri)
            startActivityForResult(intent, PHOTO_FROM_CAMERA_REQUEST_CODE)
        }
    }
}

For non‑media files, direct path access to external public directories is prohibited. Two options are recommended: store them in the app‑private directory or use SAF.

private fun startDownload(fileUrl: String, fileName: String, context: Context) {
    // Store in private external files dir
    val file = File(context.getExternalFilesDir(), fileName)
    // download logic omitted
    DownloadTask.Builder(fileUrl, file)
        .setFilename(fileName)
        .start()
}

fun installApk(apkPath: String, context: Context) {
    val file = File(apkPath)
    if (file.exists()) {
        val apkUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            Uri.fromFile(file)
        } else {
            FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
        }
        context.startActivity(Intent().apply {
            action = "android.intent.action.VIEW"
            addCategory("android.intent.category.DEFAULT")
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            setDataAndType(apkUri, "application/vnd.android.package-archive")
        })
    }
}

Direct File‑Path Access for Media Files (Android 11+) – Android 11 allows path access to shared media directories, but performance may be slower than MediaStore; using MediaStore is recommended.

Compatibility Mode – Apps can stay in legacy mode by setting targetSdkVersion ≤ 28 or adding android:requestLegacyExternalStorage="true" for targetSdkVersion 29, though this flag is ignored on Android 11+.

<manifest ...>
    <application android:requestLegacyExternalStorage="true" ...>
        ...
    </application>
</manifest>

All‑Files Access Permission (MANAGE_EXTERNAL_STORAGE) – Required for file managers or antivirus apps to read/write any file on external storage. Declare the permission, then launch the system settings intent.

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
if (!Environment.isExternalStorageManager()) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
    startActivity(intent);
}

Google restricts this permission to special app categories; misuse may block app publishing.

Recommendations

Store newly created files in the app’s private directories; no extra permissions are needed.

Migrate existing files: move media files to MediaStore collections and non‑media files to private directories.

Summary – Scoped Storage provides a safer, more organized way to handle files on Android devices. Understanding its principles, the three access methods, and the migration steps ensures smooth adaptation across Android 10, 11 and later.

Recruitment Notice – The article concludes with a campus recruitment announcement for Snowball R&D, including a link to the application page.

Mobile DevelopmentAndroidFileProviderAndroid 11MediaStoresafScoped Storage
Snowball Engineer Team
Written by

Snowball Engineer Team

Proactivity, efficiency, professionalism, and empathy are the core values of the Snowball Engineer Team; curiosity, passion, and sharing of technology drive their continuous progress.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.