Universal way to write to external SD card on Android

73,466

Solution 1

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 card on 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 before attempting 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: HTC10 and 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 Android permissions.

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

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

In order to ... write files on the external storage, your app must acquire ... system permissions:

<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 request only 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 first item 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 required if you're reading or writing only files that are private to your app. For more info..., see saving files that are 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 with these secondary shared storage devices.

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

You can then iterate over them and check Environment.getStorageState() and File.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 on external storage devices are now synthesized based on directory structure. This enables apps to manage their package-specific directories on external storage without requiring they hold the broad WRITE_EXTERNAL_STORAGE permission. For example, the app with package name com.example.foo can now freely access Android/data/com.example.foo/ on external storage devices with no permissions. These synthesized permissions are accomplished by wrapping raw storage devices in a FUSE daemon.

With KitKat your chances for a "complete solution" without rooting are pretty 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. In most areas, they can only read but not write.
  • media apps: you cannot retag/re-organize your media collection any longer, as those apps cannot write to it.
  • office apps: pretty much the same

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

The only ways to really fix that require either the manufacturer (some of them fixed it, 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 Access Framework 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 to apps 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 activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0 introduces a new runtime permissions model where apps request capabilities when needed at runtime. Because the new model includes the READ/WRITE_EXTERNAL_STORAGE permissions, the platform needs to dynamically grant storage access without killing or restarting already-running apps. It does this by maintaining three distinct views of all mounted storage devices:

  • /mnt/runtime/default is shown to apps with no special storage permissions...
  • /mnt/runtime/read is shown to apps with READ_EXTERNAL_STORAGE
  • /mnt/runtime/write is shown to apps with WRITE_EXTERNAL_STORAGE

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

Scoped Directory Access In Android 7.0, apps can use new APIs to request access to specific external storage directories, including directories on removable media such 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 in Android Q.

Specifically, the createAccessIntent() method on StorageVolume is deprecated.

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


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

Starting in Android O, the Storage Access Framework allows custom documents providers to create seekable file descriptors for files residing in a remote data source...

Permissions, prior to Android O, if an app requested a permission at runtime and the permission was granted, the system also incorrectly granted the app the rest of the permissions that belonged to the same permission 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 subsequent requests for permissions in that permission group are automatically granted.

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 everyone lives." ― 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

Solution 2

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.

Solution 3

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.

Solution 4

In addition to all other nice answers, I could add a bit more to this question so it can give wider coverage for readers. In my answer here, I would use 2 countable resources to present External Storage.

The first resource is from Android Programming, The Big Nerd Ranch Guide 2nd edition, chapter 16, page 294.

The book describes the basic and external file and directory methods. I will try to make a resume of what could be relevant to your question.

The following part from the book:

External Storage

Your photo needs more than a place on the screen. Full-size pictures are too large to stick inside a SQLite database, much less an Intent. They will need a place to live on your device’s filesystem. Normally, you would put them in your private storage. Recall that you used your private storage to save your SQLite database. With methods like Context.getFileStreamPath(String) and Context.getFilesDir(), you can do the same thing with regular files, too (which will live in a subfolder adjacent to the databases subfolder your SQLite database lives in)

Basic file and directory methods in Context

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - Returns a handle to the directory for private application files.                    |
|                                                                                       |
|FileInputStream openFileInput(String name)                                             |
| - Opens an existing file for input (relative to the files directory).                 |
|                                                                                       |
|FileOutputStream openFileOutput(String name, int mode)                                 |
| - Opens a file for output, possibly creating it (relative to the files directory).    |
|                                                                                       |
|File getDir(String name, int mode)                                                     |
| - Gets (and possibly creates) a subdirectory within the files directory.              |
|                                                                                       |
|String[] fileList()                                                                    |
| - Gets a list of file names in the main files directory, such as for use with         |
|   openFileInput(String).                                                              |
|                                                                                       |
|File getCacheDir()                                                                     |
| - Returns a handle to a directory you can use specifically for storing cache files.   |
|   You should take care to keep this directory tidy and use as little space as possible|

If you are storing files that only your current application needs to use, these methods are exactly what you need.

On the other hand, if you need another application to write to those files, you are out of luck: while there is a Context.MODE_WORLD_READABLE flag you can pass in to openFileOutput(String, int), it is deprecated, and not completely reliable in its effects on newer devices. If you are storing files to share with other apps or receiving files from other apps (files like stored pictures), you need to store them on external storage instead.

There are two kinds of external storage: primary, and everything else. All Android devices have at least one location for external storage: the primary location, which is located in the folder returned by Environment.getExternalStorageDirectory(). This may be an SD card, but nowadays it is more commonly integrated into the device itself. Some devices may have additional external storage. That would fall under “everything else.”

Context provides quite a few methods for getting at external storage, too. These methods provide easy ways to get at your primary external storage, and kinda-sorta-easy ways to get at everything else. All of these methods store files in publicly available places, too, so be careful with them.

External file and directory methods in Context

| Method                                                                                |
| --------------------------------------------------------------------------------------|
|File getExternalCacheDir()                                                             |
| - Returns a handle to a cache folder in primary external storage. Treat it like you do|
|   getCacheDir(), except a little more carefully. Android is even less likely to clean |
|   up this folder than the private storage one.                                        |
|                                                                                       |
|File[] getExternalCacheDirs()                                                          |
| - Returns cache folders for multiple external storage locations.                      |
|                                                                                       |
|File getExternalFilesDir(String)                                                       |
| - Returns a handle to a folder on primary external storage in which to store regular  |
|   files. If you pass in a type String, you can access a specific subfolder dedicated  |
|   to a particular type of content. Type constants are defined in Environment, where   |
|   they are prefixed with DIRECTORY_.                                                  |
|   For example, pictures go in Environment.DIRECTORY_PICTURES.                         |
|                                                                                       |
|File[] getExternalFilesDirs(String)                                                    |
| - Same as getExternalFilesDir(String), but returns all possible file folders for the  |
|   given type.                                                                         |
|                                                                                       |
|File[] getExternalMediaDirs()                                                          |
| - Returns handles to all the external folders Android makes available for storing     |
|   media – pictures, movies, and music. What makes this different from calling         |
|   getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner       |
|   automatically scans this folder. The media scanner makes files available to         |
|   applications that play music, or browse movies and photos, so anything that you     |
|   put in a folder returned by getExternalMediaDirs() will automatically appear in     |
|   those apps.                                                                         |

Technically, the external folders provided above may not be available, since some devices use a removable SD card for external storage. In practice this is rarely an issue, because almost all modern devices have nonremovable internal storage for their “external” storage. So it is not worth going to extreme lengths to account for it. But we do recommended including simple code to guard against the possibility, which you will do in a moment.

External storage permission

In general, you need a permission to write or read from external storage. Permissions are well-known string values you put in your manifest using the <uses-permission> tag. They tell Android that you want to do something that Android wants you to ask permission for.

Here, Android expects you to ask permission because it wants to enforce some accountability. You tell Android that you need to access external storage, and Android will then tell the user that this is one of the things your application does when they try to install it. That way, nobody is surprised when you start saving things to their SD card.

In Android 4.4, KitKat, they loosened this restriction. Since Context.getExternalFilesDir(String) returns a folder that is specific to your app, it makes sense that you would want to be able to read and write files that live there. So on Android 4.4 (API 19) and up, you do not need this permission for this folder. (But you still need it for other kinds of external storage.)

Add a line to your manifest that requests the permission to read external storage, but only up to API Listing 16.5 Requesting external storage permission (AndroidManifest.xml)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

The maxSdkVersion attribute makes it so that your app only asks for this permission on versions of Android that are older than API 19, Android KitKat. Note that you are only asking to read external storage. There is also a WRITE_EXTERNAL_STORAGE permission, but you do not need it. You will not be writing anything to external storage: The camera app will do that for you

The second resource is this link read all of it, but you can also jump to Using the External Storage section.

Reference:

More reading stuff:

Disclaimer: This information was taken from Android Programming: The Big Nerd Ranch Guide with permission from the authors. For more information on this book or to purchase a copy, please visit bignerdranch.com.

Solution 5

This topic is a bit old but I was looking for a solution and after some research I came with the code below to retrieve a list of available "external" mount points that, according to my knowledge, works on many different devices.

Basically, it reads available mount points, filters out invalid ones, tests the rest if they are accessible and adds them if all the conditions are satisfied.

Of course, required permissions must be granted before the code is invoked.

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> devices = new ArrayList<>();

    // Add default external storage if available.
    File sdCardFromSystem = null;
    switch(Environment.getExternalStorageState()) {
        case Environment.MEDIA_MOUNTED:
        case Environment.MEDIA_MOUNTED_READ_ONLY:
        case Environment.MEDIA_SHARED:
            sdCardFromSystem = Environment.getExternalStorageDirectory();
            break;
    }

    if (sdCardFromSystem != null) {
        devices.add(new FileSystemDevice(sdCardFromSystem));
    }

    // Read /proc/mounts and add all mount points that are available
    // and are not "special". Also, check if the default external storage
    // is not contained inside the mount point. 
    try {
        FileInputStream fs = new FileInputStream("/proc/mounts");
        String mounts = IOUtils.toString(fs, "UTF-8");
        for(String line : mounts.split("\n")) {
            String[] parts = line.split(" ");

            // parts[0] - mount type
            // parts[1] - mount point
            if (parts.length > 1) {
                try {

                    // Skip "special" mount points and mount points that can be accessed
                    // directly by Android's functions. 
                    if (parts[0].equals("proc")) { continue; }
                    if (parts[0].equals("rootfs")) { continue; }
                    if (parts[0].equals("devpts")) { continue; }
                    if (parts[0].equals("none")) { continue; }
                    if (parts[0].equals("sysfs")) { continue; }
                    if (parts[0].equals("selinuxfs")) { continue; }
                    if (parts[0].equals("debugfs")) { continue; }
                    if (parts[0].equals("tmpfs")) { continue; }
                    if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; }

                    // Verify that the mount point is accessible by listing its content. 
                    File file = new File(parts[1]);
                    if (file.listFiles() != null) {
                        try {

                            // Get canonical path for case it's just symlink to another mount point.
                            String devPath = file.getCanonicalPath();

                            for(FileSystemDevice device : devices) {

                                if (!devices.contains(devPath)) {                        
                                    devices.add(new FileSystemDevice(new File(devPath)));
                                }

                            }
                        } catch (Exception e) {
                            // Silently skip the exception as it can only occur if the mount point is not valid. 
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    // Silently skip the exception as it can only occur if the mount point is not valid. 
                    e.printStackTrace();
                }
            }
        }

        fs.close();
    } catch (FileNotFoundException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. 
        // Possibly, another detection method can be called here.
        e.printStackTrace();
    } catch (IOException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable.
        // Possibly, another detection method can be called here.
        e.printStackTrace();            
    }

    return devices;
}
Share:
73,466

Related videos on Youtube

Vektor88
Author by

Vektor88

Updated on July 11, 2021

Comments

  • Vektor88
    Vektor88 almost 3 years

    In my application, I need to store lots of images in the device storage. Such files tend to fulfill the device storage, and I want to allow users to be able to choose external SD card as the destination folder.

    I read everywhere that Android doesn't allow users to write to external SD card, by SD card I mean the external and mountable SD card and not the external storage, but file manager applications manage to write to External SD on all Android versions.

    What is the better way to grant read/write access to external SD card on different API levels (Pre-KitKat, KitKat, Lollipop+)?

    Update 1

    I tried Method 1 from Doomknight's answer, with no avail: As you can see I'm checking for permissions at runtime before attempting to write on SD:

    HashSet<String> extDirs = getStorageDirectories();
    for(String dir: extDirs) {
        Log.e("SD",dir);
        File f = new File(new File(dir),"TEST.TXT");
        try {
            if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) {
                f.createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

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

    10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8
    10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
    10-22 14:52:57.329 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:939)
    10-22 14:52:57.329 30280-30280/? W/System.err:     at com.myapp.activities.TestActivity.onResume(TestActivity.java:167)
    10-22 14:52:57.329 30280-30280/? W/System.err:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.Activity.performResume(Activity.java:6338)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.access$900(ActivityThread.java:150)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Looper.loop(Looper.java:168)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5885)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)
    10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.Posix.open(Native Method)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
    10-22 14:52:57.330 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:932)
    10-22 14:52:57.330 30280-30280/? W/System.err:  ... 14 more
    
    • Pavneet_Singh
      Pavneet_Singh over 7 years
      system apps can access external SD card storage completely but other apps can't unless OS and app has the root access
    • Vektor88
      Vektor88 over 7 years
      @PavneetSingh this is not true, all file explorer applications have access to external sd card, even without root.
    • Pavneet_Singh
      Pavneet_Singh over 7 years
      which file explorer you are talking about ? because some famous one , uses the rooting scripts to access sd card
    • Vektor88
      Vektor88 over 7 years
      @PavneetSingh ES file explorer, Amaze file manager, Solid Explorer and many others.
    • Pavneet_Singh
      Pavneet_Singh over 7 years
      they use the method i told you , for test just have a kitkat OS (un-rooted) and install ES and try to delete a file from it , you will get a warning(this can make your phone a brick ) asking to apply root process on your own risk
    • Vektor88
      Vektor88 over 7 years
      @PavneetSingh wrong, there is this workaround for kitkat forum.xda-developers.com/showthread.php?t=2634840
    • Pavneet_Singh
      Pavneet_Singh over 7 years
      line from the link I would strongly recommend that you NEVER rely on this code , like i said your app alone can't do it but media provider is a system app so you can exploit it feature to do what you can
    • k3b
      k3b over 7 years
      From forum.xda-developers.com : "This code should only be used to write to the secondary storage on Android 4.4+ devices if all else fails ." Since the app should (a) be usable on non-rootet phones and (b) files on sdcard must be modified are there any alternatives without rooting the phone and without changing the android standart permissions?
    • k3b
      k3b over 7 years
      according to this similar question android-sd-card-write-permission-using-saf-storage-access-fr‌​amework should be possible through SAF API, not directly via the filesystem. However i haven-t managed to get this running yet on my android 4.4.
    • Vladyslav Matviienko
      Vladyslav Matviienko over 7 years
      Here is my similar question. stackoverflow.com/q/40172011/1568530 I so far can create and write a single file on external SD card using SAF, but can't find a way to create more files in the same directory without prompt.
    • Swordsman
      Swordsman over 7 years
      try to make your targetSdkVersion >= 23
  • Vektor88
    Vektor88 over 7 years
    Environment.getExternalStorageDirectory(); will give you the path to the device internal SD card.
  • Geet Choubey
    Geet Choubey over 7 years
    Now from Android Lollipop, you get two options, to use External SD Card as Internal storage or like a typical sd card, it depends on that selection too.
  • Geet Choubey
    Geet Choubey over 7 years
  • Vektor88
    Vektor88 over 7 years
    That piece of code gives me the path where the external SD card is mounted. What then? I still can't write on it.
  • Geet Choubey
    Geet Choubey over 7 years
    What error are you getting when you try to write to external sd card?
  • Eugen Pechanec
    Eugen Pechanec over 7 years
    Please share a link where you got the code in method 1. Credit where it's due. Thanks.
  • IAmGroot
    IAmGroot over 7 years
    @EugenPechanec I completely agree. however it was years ago i sourced this code from a fantastic source. I will have a look for it
  • Vektor88
    Vektor88 over 7 years
    It's not clear to me what should I do with these paths: If the device is not rooted, I won't get write access to the sdcard by specifying its absolute path. Am I wrong? Maybe I can store some data in dedicated app folder in /external_sd/data/data/com.myapp..., but I won't be able to write to /external_sd/MyApp/Images
  • IAmGroot
    IAmGroot over 7 years
    @Vektor88 You just request write access to the SDCards as per normal, unless using the dedicated folders which dont require it. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> No Rooting is necessary
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight I'm afraid method 1 requires root, since I'm unable to access /mnt/media_rw as regular user
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight while method 2 should work, but won't allow me to write to root of external sd
  • IAmGroot
    IAmGroot over 7 years
    @vektor88 You can access it fine without rooting. I nor my clients ever use rooted devices. What error are you getting? Did you add the permission. And as stated below are you targeting the latest and request permission at run time. Change target to 20 and see if it works, before resolving run time permission request. Else provide error please.
  • Vektor88
    Vektor88 over 7 years
    My deployed apk already targets api 23, I cannot rollback to api 20
  • IAmGroot
    IAmGroot over 7 years
    @Vektor88 That concept is purely as a test, to see if you are now having permission issues. If you are targeting api 23, then it bids the question, did you request permissions on run time. Although this is a separate issue to the desire to get the SDCard location for saving (as origionally questioned). developer.android.com/training/permissions/requesting.html
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight yes, I can write to Environment.getExternalStorageDirectory() which actually points to my device internal storage.
  • IAmGroot
    IAmGroot over 7 years
    @Vektor88 You will need to provide a logcat of the error you are getting. It would/should instantly tell you were the issue is.
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight It tells me that I have no access to that folder and therefore I cannot write on it.
  • IAmGroot
    IAmGroot over 7 years
    You are probably best posting the actual log cat message. THe exact message may help further. As at the moment I do not know why you would not have access, unless its a permissions issue. But you state permissions work as you can access the internal SD card. The code above does work, as I use it all the time on many different devices. So failing that, its a device specific issue I have never come across.But again, exact logcat would help trace the issue. Though it may be better in a separate question. Aka, "this is the location i am saving to, and this is the error."
  • Vektor88
    Vektor88 over 7 years
    java.io.IOException: open failed: EACCES (Permission denied) it thrown at the line where, for instance, i try new File(sd,"test.file").createNewFile()
  • IAmGroot
    IAmGroot over 7 years
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight as I said, I ask permissions at runtime
  • IAmGroot
    IAmGroot over 7 years
    @Vektor88 . Sorry, but it indicates a permissions issue, are you sure you have done it properly? Did you ever change the target API to ~20 to quickly check if it functions?
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight If I can write to Environment.getExternalStorageDirectory I'm quite sure that permissions are not the problem here.
  • IAmGroot
    IAmGroot over 7 years
    @Vektor88 Then I am unable to help sorry. If you are getting permission issues yet you have correctly set the permissions, and have tested such, then I do not know why this doesn't work for you. As suggested, you should take the path, permission code/file access code, and error, and open another question as to why you are being denied permission.
  • Vektor88
    Vektor88 over 7 years
    @Doomsknight I updated the question, maybe it will give you further information.
  • IAmGroot
    IAmGroot over 7 years
    A very indepth analysis. +1 . Though i must note, i did not write that method. It is from another source a couple of years ago :) Hope it helps him.
  • Vektor88
    Vektor88 over 7 years
    Thank you for the complete answer. Seems to me that the problem here is that I should use SAF to write in the root of my SD card instead of using @Doomsknight method 1, because it won't work on higher API levels, while below KitKat I can use it.
  • albodelu
    albodelu over 7 years
    Np, if you need to write outside your app dir, seems the correct way to use the official API. Seems here solved a similar issue, I'll check it another day.
  • albodelu
    albodelu over 7 years
    Thanks @th3pat3l isExternalStorageRemovable reference developer.android.com/reference/android/os/…
  • Theo
    Theo over 7 years
    Vektor88, despite all the contributions above, none of them actually resolved your issue of writing to the external removable sdcard for Android 6 or above. Did you manage to resolve this?
  • MarkJoel60
    MarkJoel60 over 7 years
    This only works to access to the external SD card subdirectory based on your application: e.g. on my LG Phone it gives me: /storage/0403-0201/Android/data/com.your.application/files -- if you want to add a file to the DCIM directory -- even if you have permissions in your manifest and you ask for them at runtime, and you go into you application->permissions under set up and turn Storage on YOU STILL GET ENOENT (No such file or directory) error if you try to create and open a file there. At least that's what is happening on my LG Aristo SDK 23. The only thing I see that works is SAF.
  • karanatwal.github.io
    karanatwal.github.io almost 7 years
    How ES file explorer in 4.4.2 phones creating Folder ? @albodelu Can you please explain
  • albodelu
    albodelu almost 7 years
    In point 3, there is a link where it's explained how to do it for rooted devices. This app has an option to disable the limitation added in kitKat: Tool > Root Explorer > Mount R/W
  • Ankit Srivastava
    Ankit Srivastava almost 7 years
    @albodelu aren't Android 5 and 6 very similar to the way they grant permissions ? I thought we can also grant permissions at runtime in Android 6 for SD-CARD. Can I talk to you in private ?
  • user924
    user924 over 6 years
    Method 2 is equal to Context.getExternalCacheDirs(), but support method is better of course because Context.getExternalCacheDirs() works only for >= 19 API
  • user924
    user924 over 6 years
    isExternalStorageRemovable is only for >= 21, is it possible to determine if storage is removable (microsd card) on <= 19 API devices, implementing similar method somehow?
  • albodelu
    albodelu over 6 years
    Sorry, I only documented Doomsknight's answer to learn about this topic a year ago, and I am currently busy with another funny task (background streaming) my client wants. I recommend you to check my latest edition, or to create a new SO Q, so experts on this issue can satisfy you.
  • Basj
    Basj over 6 years
    @albodelu Would you have an idea for (similar problem) stackoverflow.com/questions/47734937/…?
  • Doopy
    Doopy about 5 years
  • Andrew S
    Andrew S about 2 years
    I really appreciate such insights, but my goodness what a hot mess google have created.