Universal way to write to external SD card on Android Universal way to write to external SD card on Android android android

Universal way to write to external SD card on Android


Summary

You can grant read/write access to external SD card on the different api levels (API23+ at run time).

Since KitKat, permissions are not necessary if you use app-specific directories, required otherwise.

Universal way:

The history says that there is no universal way to write to external SD card but continues...

This fact is demonstrated by these examples of external storage configurations for devices.

API-based way:

Prior to KitKat try to use Doomsknight method 1, method 2 otherwise.

Request permissions in manifest (Api < 23) and at run time (Api >= 23).

Recommended way:

ContextCompat.getExternalFilesDirs solves the access error when you don't need to share files.

The secure way of sharing it is to use a content provider or the new Storage Access Framework.

Privacy-aware way:

As of Android Q Beta 4, apps that target Android 9 (API level 28) or lower see no change, by default.

Apps targeting Android Q by default (or opting into it) are given a filtered view into external storage.


  1. Initial answer.

Universal way to write to external SD card on Android

There is no universal way to write to external SD card on Android due to continuous changes:

  • Pre-KitKat: official Android platform has not supported SD cards at all except for exceptions.

  • KitKat: introduced APIs that let apps access files in app-specific directories on SD cards.

  • Lollipop: added APIs to allow apps to request access to folders owned by other providers.

  • Nougat: provided a simplified API to access common external storage directories.

  • ... Android Q privacy change: App-scoped and media-scoped storage

What is the better way to grant read/write access to external SD cardon different API levels

Based on Doomsknight's answer and mine, and Dave Smith and Mark Murphy blog posts: 1, 2, 3:


  1. Updated answer.

Update 1. I tried Method 1 from Doomknight's answer, with no avail:

As you can see I'm checking for permissions at runtime beforeattempting to write on SD...

I would use application-specific directories to avoid the issue of your updated question and ContextCompat.getExternalFilesDirs() using getExternalFilesDir documentation as reference.

Improve the heuristics to determine what represents removable media based on the different api levels like android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

... But I get an access error, tried on two different devices: HTC10and Shield K1.

Remember that Android 6.0 supports portable storage devices and third-party apps must go through the Storage Access Framework. Your devices HTC10 and Shield K1 are probably API 23.

Your log shows a permission denied exception accessing /mnt/media_rw, like this fix for API 19+:

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" ><group gid="sdcard_r" /><group gid="sdcard_rw" /><group gid="media_rw" /> // this line is added via root in the link to fix it.</permission>

I never tried it so I can not share code but I would avoid the for trying to write on all the returned directories and look for the best available storage directory to write into based on remaining space.

Perhaps Gizm0's alternative to your getStorageDirectories() method it's a good starting point.

ContextCompat.getExternalFilesDirs solves the issue if you don't need access to other folders.


  1. Android 1.0 .. Pre-KitKat.

Prior to KitKat try to use Doomsknight method 1 or read this response by Gnathonic.

public static HashSet<String> getExternalMounts() {    final HashSet<String> out = new HashSet<String>();    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";    String s = "";    try {        final Process process = new ProcessBuilder().command("mount")                .redirectErrorStream(true).start();        process.waitFor();        final InputStream is = process.getInputStream();        final byte[] buffer = new byte[1024];        while (is.read(buffer) != -1) {            s = s + new String(buffer);        }        is.close();    } catch (final Exception e) {        e.printStackTrace();    }    // parse output    final String[] lines = s.split("\n");    for (String line : lines) {        if (!line.toLowerCase(Locale.US).contains("asec")) {            if (line.matches(reg)) {                String[] parts = line.split(" ");                for (String part : parts) {                    if (part.startsWith("/"))                        if (!part.toLowerCase(Locale.US).contains("vold"))                            out.add(part);                }            }        }    }    return out;}

Add the next code to your AndroidManifest.xml and read Getting access to external storage

Access to external storage is protected by various Androidpermissions.

Starting in Android 1.0, write access is protected withthe WRITE_EXTERNAL_STORAGE permission.

Starting in Android 4.1, readaccess is protected with the READ_EXTERNAL_STORAGE permission.

In order to ... write files on the external storage, your app mustacquire ... systempermissions:

<manifest ...>    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /></manifest> 

If you need to both..., you need to requestonly the WRITE_EXTERNAL_STORAGE permission.

Read Mark Murphy's explanation and recommended Dianne Hackborn and Dave Smith posts

  • Until Android 4.4, there was no official support for removable media in Android, Starting in KitKat, the concept of “primary” and “secondary” external storage emerges in the FMW API.
  • Prior apps are just relying on MediaStore indexing, ship with the hardware or examine mount points and apply some heuristics to determine what represents removable media.

  1. Android 4.4 KitKat introduces the Storage Access Framework (SAF).

Ignore the next note due to bugs, but try to use ContextCompat.getExternalFilesDirs():

  • Since Android 4.2, there has been a request from Google for device manufacturers to lock down removable media for security (multi-user support) and new tests were added in 4.4.
  • Since KitKat getExternalFilesDirs() and other methods were added to return a usable path on all available storage volumes (The firstitem returned is the primary volume).
  • The table below indicates what a developer might try to do and how KitKat will respond:enter image description here

Note: Beginning with Android 4.4, these permissions are not requiredif you're reading or writing only files that are private to your app.For more info..., see saving files thatare app-private.

<manifest ...>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"                     android:maxSdkVersion="18" /></manifest>

Also read Paolo Rovelli's explanation and try to use Jeff Sharkey's solution since KitKat:

In KitKat there's now a public API for interacting withthese secondary shared storage devices.

The new Context.getExternalFilesDirs() andContext.getExternalCacheDirs() methods can return multiple paths,including both primary and secondary devices.

You can then iterateover them and check Environment.getStorageState() andFile.getFreeSpace() to determine the best place to store your files.

These methods are also available on ContextCompat in the support-v4 library.

Starting in Android 4.4, the owner, group and modes of files onexternal storage devices are now synthesized based on directorystructure. This enables apps to manage their package-specificdirectories on external storage without requiring they hold the broadWRITE_EXTERNAL_STORAGE permission. For example, the app with packagename com.example.foo can now freely accessAndroid/data/com.example.foo/ on external storage devices with nopermissions. These synthesized permissions are accomplished bywrapping raw storage devices in a FUSE daemon.

With KitKat your chances for a "complete solution" without rooting arepretty much zero:

The Android project has definitely screwed up here.No apps get full access to external SD cards:

  • file managers: you cannot use them to manage your external SD card. Inmost areas, they can only read but not write.
  • media apps: you cannotretag/re-organize your media collection any longer, as those appscannot write to it.
  • office apps: pretty much the same

The only place 3rd party apps are allowed to write on yourexternal card are "their own directories" (i.e./sdcard/Android/data/<package_name_of_the_app>).

The only ways toreally fix that require either the manufacturer (some of them fixedit, e.g. Huawei with their Kitkat update for the P6) – or root... (Izzy's explanation continues here)


  1. Android 5.0 introduced changes and the DocumentFile helper class.

getStorageState Added in API 19, deprecated in API 21,use getExternalStorageState(File)

Here's a great tutorial for interacting with the Storage AccessFramework in KitKat.

Interacting with the new APIs in Lollipop is very similar (Jeff Sharkey's explanation).


  1. Android 6.0 Marshmallow introduces a new runtime permissions model.

Request permissions at runtime if API level 23+ and read Requesting Permissions at Run Time

Beginning in Android 6.0 (API level 23), users grant permissions toapps while the app is running, not when they install the app ... or update the app ... user can revoke the permissions.

// Assume thisActivity is the current activityint permissionCheck = ContextCompat.checkSelfPermission(thisActivity,        Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0 introduces a new runtime permissions model where appsrequest capabilities when needed at runtime. Because the new modelincludes the READ/WRITE_EXTERNAL_STORAGE permissions, the platformneeds to dynamically grant storage access without killing orrestarting already-running apps. It does this by maintaining threedistinct views of all mounted storage devices:

  • /mnt/runtime/default is shown to apps with no special storagepermissions...
  • /mnt/runtime/read is shown to apps withREAD_EXTERNAL_STORAGE
  • /mnt/runtime/write is shown to apps withWRITE_EXTERNAL_STORAGE

  1. Android 7.0 provides a simplified API to access external storage dirs.

Scoped Directory AccessIn Android 7.0, apps can use new APIs to request access to specificexternal storage directories, including directories on removable mediasuch as SD cards...

For more information, see the Scoped Directory Access training.

Read Mark Murphy posts: Be Careful with Scoped Directory Access. It was deprecated in Android Q:

Note that the scoped directory access added in 7.0 is deprecated inAndroid Q.

Specifically, the createAccessIntent() method on StorageVolume isdeprecated.

They added a createOpenDocumentTreeIntent() that can be used as analternative.


  1. Android 8.0 Oreo .. Android Q Beta changes.

Starting in AndroidO, theStorage Access Framework allows custom documentsprovidersto create seekable file descriptors for files residing in a remotedata source...

Permissions,prior to Android O, if an app requested a permission at runtime and the permission was granted, the system also incorrectly grantedthe app the rest of the permissions that belonged to the samepermission group, and that were registered in the manifest.

For apps targeting Android O, this behavior has been corrected. The app is granted only the permissions it has explicitly requested.However, once the user grants a permission to the app, all subsequentrequests for permissions in that permission group are automaticallygranted.

For example, READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE...

Update: An Android Q earlier beta release temporarily replaced the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions with more fine-grained, media-specific permissions.

Note: Google introduced roles on Beta 1 and removed them from the documentation before Beta 2...

Note: The permissions specific to media collections that were introduced in earlier beta releases—READ_MEDIA_IMAGES, READ_MEDIA_AUDIO, and READ_MEDIA_VIDEOare now obsolete. More info:

Q Beta 4 (final APIs) review by Mark Murphy: The Death of External Storage: The End of the Saga(?)

"Death is more universal than life. Everyone dies, but not everyonelives." ― Andrew Sachs


  1. Related questions and recommended answers.

How can I get external SD card path for Android 4.0+?

mkdir() works while inside internal flash storage, but not SD card?

Diff between getExternalFilesDir and getExternalStorageDirectory()

Why getExternalFilesDirs() doesn't work on some devices?

How to use the new SD card access API presented for Android 5.0 (Lollipop)

Writing to external SD card in Android 5.0 and above

Android SD Card Write Permission using SAF (Storage Access Framework)

SAFFAQ: The Storage Access Framework FAQ


  1. Related bugs and issues.

Bug: On Android 6, when using getExternalFilesDirs, it won't let you create new files in its results

Writing to directory returned by getExternalCacheDir() on Lollipop fails without write permission


I believe there are two methods to achieve this:

METHOD 1: (does NOT work on 6.0 and above, due to permission changes)

I have been using this method for years on many device version with no issue. Credit is due to the original source, as it was not me who wrote it.

It will return all mounted media (including Real SD Cards) in a list of strings directory locations. With the list you can then ask the user where to save, etc.

You can call it with the following:

 HashSet<String> extDirs = getStorageDirectories();

Method:

/** * Returns all the possible SDCard directories */public static HashSet<String> getStorageDirectories() {    final HashSet<String> out = new HashSet<String>();    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";    String s = "";    try {        final Process process = new ProcessBuilder().command("mount")                .redirectErrorStream(true).start();        process.waitFor();        final InputStream is = process.getInputStream();        final byte[] buffer = new byte[1024];        while (is.read(buffer) != -1) {            s = s + new String(buffer);        }        is.close();    } catch (final Exception e) {        e.printStackTrace();    }    // parse output    final String[] lines = s.split("\n");    for (String line : lines) {        if (!line.toLowerCase().contains("asec")) {            if (line.matches(reg)) {                String[] parts = line.split(" ");                for (String part : parts) {                    if (part.startsWith("/"))                        if (!part.toLowerCase().contains("vold"))                            out.add(part);                }            }        }    }    return out;}

METHOD 2:

Use the v4 support library

import android.support.v4.content.ContextCompat;

Just call the following to get a list of File locations of storage.

 File[] list = ContextCompat.getExternalFilesDirs(myContext, null);

The locations differ in usage however though.

Returns absolute paths to application-specific directories on all external storage devices where the application can place persistent files it owns. These files are internal to the application, and not typically visible to the user as media.

External storage devices returned here are considered a permanent part of the device, including both emulated external storage and physical media slots, such as SD cards in a battery compartment. The returned paths do not include transient devices, such as USB flash drives.

An application may store data on any or all of the returned devices. For example, an app may choose to store large files on the device with the most available space

More Info on ContextCompat

They are like app specific files. Hidden from other apps.


Just another answer. This answer only shows 5.0+ because I believe Doomknight's answer posted here is the best way to do for Android 4.4 and below.

This is originally posted here (Is there a way to get SD Card size in Android?) by me to get the external SD Card's size on Android 5.0+

To get the External SD card as a File:

public File getExternalSdCard() {    File externalStorage = null;    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {        File storage = new File("/storage");        if(storage.exists()) {            File[] files = storage.listFiles();            for (File file : files) {                if (file.exists()) {                    try {                        if (Environment.isExternalStorageRemovable(file)) {                            externalStorage = file;                            break;                        }                    } catch (Exception e) {                        Log.e("TAG", e.toString());                    }                }            }        }    } else {        // do one of many old methods        // I believe Doomsknight's method is the best option here    }    return externalStorage;}

Note: I only get the "first" external sd card however you can modify it and return ArrayList<File> instead of File and let the loop continue instead of calling break after the first one is found.