Find location of a removable SD card
Asked Answered
J

25

219

Is there a universal way to find the location of an external SD card?

Please, do not be confused with External Storage.

Environment.getExternalStorageState() returns the path to the internal SD mount point, such as /mnt/sdcard. But the question is about the external SD. How do I get a path like /mnt/sdcard/external_sd (it may differ from device to device)?

I guess I will end with filtering of the output of the mount command by filesystem name. But I'm not sure this way is robust enough.

Janellajanelle answered 17/4, 2011 at 16:57 Comment(3)
Here's my solution which works till Nougat: https://mcmap.net/q/128185/-how-to-get-sd_card-path-in-android6-0-programmaticallyVentriculus
'Environment.getExternalStorageState() returns path to internal SD mount point like "/mnt/sdcard".' Well, it's not internal in the sense that Android uses the term. I believe the term you're looking for is "not removable."Meneses
adb shell echo $EXTERNAL_STORAGEFilippo
D
171

Environment.getExternalStorageState() returns path to internal SD mount point like "/mnt/sdcard"

No, Environment.getExternalStorageDirectory() refers to whatever the device manufacturer considered to be "external storage". On some devices, this is removable media, like an SD card. On some devices, this is a portion of on-device flash. Here, "external storage" means "the stuff accessible via USB Mass Storage mode when mounted on a host machine", at least for Android 1.x and 2.x.

But the question is about external SD. How to get a path like "/mnt/sdcard/external_sd" (it may differ from device to device)?

Android has no concept of "external SD", aside from external storage, as described above.

If a device manufacturer has elected to have external storage be on-board flash and also has an SD card, you will need to contact that manufacturer to determine whether or not you can use the SD card (not guaranteed) and what the rules are for using it, such as what path to use for it.


UPDATE

Two recent things of note:

First, on Android 4.4+, you do not have write access to removable media (e.g., "external SD"), except for any locations on that media that might be returned by getExternalFilesDirs() and getExternalCacheDirs(). See Dave Smith's excellent analysis of this, particularly if you want the low-level details.

Second, lest anyone quibble on whether or not removable media access is otherwise part of the Android SDK, here is Dianne Hackborn's assessment:

...keep in mind: until Android 4.4, the official Android platform has not supported SD cards at all except for two special cases: the old school storage layout where external storage is an SD card (which is still supported by the platform today), and a small feature added to Android 3.0 where it would scan additional SD cards and add them to the media provider and give apps read-only access to their files (which is also still supported in the platform today).

Android 4.4 is the first release of the platform that has actually allowed applications to use SD cards for storage. Any access to them prior to that was through private, unsupported APIs. We now have a quite rich API in the platform that allows applications to make use of SD cards in a supported way, in better ways than they have been able to before: they can make free use of their app-specific storage area without requiring any permissions in the app, and can access any other files on the SD card as long as they go through the file picker, again without needing any special permissions.

Donela answered 17/4, 2011 at 17:30 Comment(16)
And this problem is becoming more of an issue as HC and ICS devices come out that point "ExternalStorageDirectory" and everything else like it to internal physical storage. Top it off that most users have absolutely no clue how to locate where their sdcard is in the filesystem.Gwenn
So your answer is basically 'contact the manufacturer'. Not useful.Fuddyduddy
The last part of the answer is not quite accurate -- it is indeed possible to detect the SD card path by following the answers below this (scanning /proc/mounts, /system/etc/vold.fstab, etc...).Annexation
@Kevin: Only fools make assumptions about the behavior of the device outside the SDK. For example, I will be surprised if the other techniques you cite will work reliably on Android 4.2+ tablets with secondary accounts. There may well be devices that lack a mount command, let alone one in the PATH, as one answer assumes. And the comments on the other answers (or sometimes the answer itself) indicates that the solution is not reliable and does not work on all devices.Donela
@CommonsWare: Nonetheless, it's still not accurate that one "needs" to contact a manufacturer when there are solutions that work on many devices out there, and besides, the SDK itself is not consistent on all devices so that is no guarantee. Even if these solutions don't work on all devices, they work on enough devices that many Android apps on the market rely on these techniques, among others, to detect the external SD card path. I think it's a little harsh and premature to call all of these developers fools -- isn't the customer surely the final judge of that?Annexation
@Kevin: "I think it's a little harsh and premature to call all of these developers fools" -- you would have said the same thing about my warning developers against using undocumented content:// values, validated with the Gmail lockdown. Or my advice to not try back-door ways to access LogCat on the device, validated when 4.2 locked that down. And so on. Multiple external storage locations is an undeniable gap in the SDK, which hopefully will get fixed. In the meantime, I will continue to counsel against intentionally writing unreliable apps. You, of course, are welcome to do as you wish.Donela
@Donela That is fair enough, as things go. I definitely agree with you that a developer can't assume this will always work everywhere, and that any such code can't be guaranteed to work on all devices or for all versions of Android. Hopefully it does get fixed in the SDK! In the meantime, there are still options that do work on many devices and can improve the end-user experience, and given the choice between 80% success and 0% success I'll take the 80%.Annexation
i've noticed that all devices nowadays (that i've tried) have the external storage being built into the device. is this true? also, what is the correct way to get the list of external-sd-card paths, and also USBOTG (like diskonkey) ? is it true that as of kitkat, you can't write to the external-sd-card anywhere you wish, unless your app is a system app ?Yarmouth
"is this true?" -- for the past few years, yes. "what is the correct way to get the list of external-sd-card paths, and also USBOTG (like diskonkey) ?" -- there is no correct way, from the standpoint of the Android SDK. "is it true that as of kitkat, you can't write to the external-sd-card anywhere you wish, unless your app is a system app ?" -- I do not have an Android 4.4 device with an SD card slot, so I have been unable to test this scenario yet.Donela
I have android 4.4.4 and my app can write to external storage root using Environment.getExternalStorageDirectory().toString() + "/" + "Folder Name"?Why?Smallminded
but my target sdk is android:targetSdkVersion="18"? is it some thing that can affect?Smallminded
Android obviously do has a concept of external SD card, because may of applications can be configured to store it's data on external SD cards.Iaria
@Dims: The question and answer that you are commenting on are from 2011, at which time there was no official support for removable storage in the Android SDK. That was added with Android 4.4; the 2014 update to my answer covers this.Donela
@Donela can we twist StorageAccessFramework to somehow provide us a with a list of both removable and non removable storage directories without showing its built-in UI ? I don't know about SAF much, but i contacted this open source file manager apps' creator who said to use SAF for getting both the memory card and non removable external storage at the same time. Perhaps you could give a simpler solutionCariotta
@anshsachdeva: The Storage Access Framework is pretty much defined by "showing its built-in UI". So, no, I do not know of a way to achieve what you want. Note that Android Q has a new API on MediaStore to get volume names for external and removable storage.Donela
@LMKIND: The writeup was excellent back in 2014 and is only relevant for really old versions of Android now. FWIW, here is an archive.org copy of the post.Donela
A
65

I came up with the following solution based on some answers found here.

CODE:

public class ExternalStorage {

    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    /**
     * @return True if the external storage is available. False otherwise.
     */
    public static boolean isAvailable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static String getSdCardPath() {
        return Environment.getExternalStorageDirectory().getPath() + "/";
    }

    /**
     * @return True if the external storage is writable. False otherwise.
     */
    public static boolean isWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;

    }

    /**
     * @return A map of all storage locations available
     */
    public static Map<String, File> getAllStorageLocations() {
        Map<String, File> map = new HashMap<String, File>(10);

        List<String> mMounts = new ArrayList<String>(10);
        List<String> mVold = new ArrayList<String>(10);
        mMounts.add("/mnt/sdcard");
        mVold.add("/mnt/sdcard");

        try {
            File mountFile = new File("/proc/mounts");
            if(mountFile.exists()){
                Scanner scanner = new Scanner(mountFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("/dev/block/vold/")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        // don't add the default mount path
                        // it's already in the list.
                        if (!element.equals("/mnt/sdcard"))
                            mMounts.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            File voldFile = new File("/system/etc/vold.fstab");
            if(voldFile.exists()){
                Scanner scanner = new Scanner(voldFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":"))
                            element = element.substring(0, element.indexOf(":"));
                        if (!element.equals("/mnt/sdcard"))
                            mVold.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        for (int i = 0; i < mMounts.size(); i++) {
            String mount = mMounts.get(i);
            if (!mVold.contains(mount))
                mMounts.remove(i--);
        }
        mVold.clear();

        List<String> mountHash = new ArrayList<String>(10);

        for(String mount : mMounts){
            File root = new File(mount);
            if (root.exists() && root.isDirectory() && root.canWrite()) {
                File[] list = root.listFiles();
                String hash = "[";
                if(list!=null){
                    for(File f : list){
                        hash += f.getName().hashCode()+":"+f.length()+", ";
                    }
                }
                hash += "]";
                if(!mountHash.contains(hash)){
                    String key = SD_CARD + "_" + map.size();
                    if (map.size() == 0) {
                        key = SD_CARD;
                    } else if (map.size() == 1) {
                        key = EXTERNAL_SD_CARD;
                    }
                    mountHash.add(hash);
                    map.put(key, root);
                }
            }
        }

        mMounts.clear();

        if(map.isEmpty()){
                 map.put(SD_CARD, Environment.getExternalStorageDirectory());
        }
        return map;
    }
}

USAGE:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
Agosto answered 25/3, 2013 at 10:43 Comment(12)
tested with nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)Agosto
Hi again, Richard - believe it or not, I have to ask: did you actually try writing out and reading back in a file this way, not just get the dirs? Recall our old "/sdcard0" issue ? I tried this code and it failed on an S3 when I tried to read back in the file that it did write. ... this is very bizarre ... and painful :))Acea
This fails on devices that doesn't have 2 SD cards. It assumes 1st found is internal, and 2nd found is external...Biodegradable
Did NOT worked for USB devices attached through OTG cable on Nexus 5 and Nexus 7.Drye
/system/etc/vold.fstab is not accessible in android 4.3+Micromillimeter
This worked well for going between Samsung tablet and PQ Labs iStick.Cheshvan
Any idea on how to fix this for 4.3+. This does not find SD card storage on 4.3+.Heroic
I added a test for whether vold.fstab exists, and if it doesn't exist, skip removing the mounts absent from mVold. gist.github.com/paour/11403182Entozoon
My issue is that for 4.3+ the vold.fstab is not readable. In this case, I simply try to use what I read from "/proc/mounts", however, the directory I find there is not writeable. I am however able to access this directory from other apps (i.e. file managers), so it is readable to some applications. ANyone have any idea why this might be happening?Crossfertilization
Attempt to invoke virtual method 'java.lang.String java.io.File.getAbsolutePath()' on a null object reference while trying to get path of external sd card. And there is removable sdcard is availabale. I am using Lenova A7010a48 with Marshmallow.Jenicejeniece
first returns /mnt/sdcard which doesn't show any folders, second nullPillory
Downvoted because return Environment.getExternalStorageDirectory().getPath() + "/", I skipped the rest.Clarke
S
38

I had an application that used a ListPreference where the user was required to select the location of where they wanted to save something.

In that app, I scanned /proc/mounts and /system/etc/vold.fstab for sdcard mount points. I stored the mount points from each file into two separate ArrayLists.

Then, I compared one list with the other and discarded items that were not in both lists. That gave me a list of root paths to each sdcard.

From there, I tested the paths with File.exists(), File.isDirectory(), and File.canWrite(). If any of those tests were false, I discarded that path from the list.

Whatever was left in the list, I converted to a String[] array so it could be used by the ListPreference values attribute.

You can view the code here

Sydel answered 16/2, 2012 at 17:14 Comment(3)
@fifth, Samsung claims we need to append "/external_sd/" to the String returned by Environment.getExternalStorageDirectory() and then append "/filename.foo". Only problem is, that does not work on three S3 devices I tested - but it works on their RemoteTestLab's "real" devices. This is killing me ...Acea
ok, but what about "worst case" - say you have a Samsung that has /sdcard (internal), /exteranl_sd/ for physical 32GB microSD card, and then you pop on a USB hard drive AND perhaps a SSHFS network mount. Is this code going to find all 4? Reference for SSH mount: github.com/l3iggs/android_external_sshfsLikeness
Useful for me as well, but I will give a warning: The code checks if the directory is writable, and if your app is read-only (and therefore doesn't request write permission), then you will need to modify the code slightly.Mayman
Y
27

You can try to use the support library function called of ContextCompat.getExternalFilesDirs() :

      final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
      final ArrayList<File> extRootPaths=new ArrayList<>();
      for(final File file : appsDir)
        extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());

The first one is the primary external storage, and the rest are supposed to be real SD-cards paths.

The reason for the multiple ".getParentFile()" is to go up another folder, since the original path is

.../Android/data/YOUR_APP_PACKAGE_NAME/files/

EDIT: here's a more comprehensive way I've created, to get the sd-cards paths:

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }

Edit: better solution here:

https://mcmap.net/q/128186/-how-can-i-get-the-external-sd-card-path-for-android-4-0

Yarmouth answered 1/9, 2014 at 21:1 Comment(16)
It seems a very good answer, but how would one integrate this in a simple activity? There are several variables not defined, like App, ContextCompact, EnvironmentCompactAugite
@Augite ContextCompact, EnvironmentCompact are available via the support library. The "App.global()" is just the application context, which I've set globally since I don't like adding a Context parameter everywhere.Yarmouth
Great! Works for mine device v4.4 Samsung GT S Advance, hope it will be working for othersPillory
@androiddeveloper Will the edited answer work for all Devices and SD Card Sizes?Danadanae
@Danadanae I hope so. Haven't tried it in a while, because sadly most devices out there are now without SD card slot.Yarmouth
@androiddeveloper Thank You very much will surely give it a try. Have tried many other methods which have not been able to provide results.Danadanae
This worked perfectly for me - should be the accepted answer.Paduasoy
This will incluide emulated external storage as I tested on my LG G3, because its state is "mounted" as wellHerder
@MarioVelasco Is it a good thing or a bad thing? And have you somehow find a fix for it, if it's a bad thing?Yarmouth
@androiddeveloper it is a bad thing because you will get internal memory because that "external storage" is emulated. You can use getExternalFilesDirs(String type) to get all "external directories" emulated or not and you can use Environment.isExternalStorageEmulated() to know if the first "external storage" given is emulated or not.Herder
@MarioVelasco It's not just the first. You can choose others too: developer.android.com/reference/android/os/… . What you offer is that if it's emulated, it shouldn't be on the list?Yarmouth
@androiddeveloper what I offered is for the primary external storage (the first) but you can use the same method with path as parameter to know all the rest of external storages if needed.Herder
@MarioVelasco I mean, does your idea mean that I will ignore each of the emulated ones?Yarmouth
@androiddeveloper Yes. As a solution, I wouldn't consider an emulated external storage like an SD cardHerder
The approach is good, however getRootOfInnerSdCardFolder() seems not traverse up to the storage root with Android 11+Darla
@AlexeyOzerov Try the new link. There I've implemented getRootOfInnerSdCardFolder better.Yarmouth
A
17

In order to retrieve all the External Storages (whether they are SD cards or internal non-removable storages), you can use the following code:

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}

Alternatively, you might use System.getenv("EXTERNAL_STORAGE") to retrieve the primary External Storage directory (e.g. "/storage/sdcard0") and System.getenv("SECONDARY_STORAGE") to retieve the list of all the secondary directories (e.g. "/storage/extSdCard:/storage/UsbDriveA:/storage/UsbDriveB"). Remember that, also in this case, you might want to filter the list of secondary directories in order to exclude the USB drives.

In any case, please note that using hard-coded paths is always a bad approach (expecially when every manufacturer may change it as pleased).

Avrilavrit answered 7/11, 2013 at 9:7 Comment(7)
Just consider any downvoter who doesn't leave a comment a troll, so I upvoted to compensate for it. ;) BUT, I guess your method is rather arbitrary: how can we know that skipping those "USB drives" but keeping all else really is equal to "sdcards", as asked in the question? Also your suggested System.getenv("SECONDARY_STORAGE") could also do with some references, as it seems undocumented.Sense
As far as I know, in the Android API there is no reference of a standard method to retrieve all the External Storages. However, the method proposed is not arbitrary at all. In Android, like in every Unix/Linux system, ALL the mounting storage devices are stored/linked in a common directory: "/mnt" (the standard Unix/Linux directory for mounting storage devices) or, in the newest versions, "/storage". That is why you can be pretty sure you will find all the SD cards linked in this folder.Avrilavrit
Regarding the System.getenv("EXTERNAL_STORAGE") method, I do not have any reference rather than the API page (which does not explain a lot): developer.android.com/reference/java/lang/… I could not find any official page for the Android system environment variables. Here, however, you can find a short list of them: herongyang.com/Android/…Avrilavrit
What I meant by not being sure about sdcards is that in /mnt there can be various other fs trees, too, not just SD cards and USB drives. Your code would also list any internal (perhaps even virtual) filesystem mounts, if I understand it right, while the question wants sdcards only.Sense
I see. Yes, you are right. With my method you will retrieve also the internal (non-removable) SD memories.Avrilavrit
System.getenv("SECONDARY_STORAGE") did NOT worked for USB devices attached through OTG cable on Nexus 5 and Nexus 7.Drye
externalStorageRoot.listFiles() returns null on HTC One M9 and thus crashesMicrotome
T
16

Like Richard I also use /proc/mounts file to get the list of available storage options

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

        public final String path;
        public final boolean internal;
        public final boolean readonly;
        public final int display_number;

        StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
            this.path = path;
            this.internal = internal;
            this.readonly = readonly;
            this.display_number = display_number;
        }

        public String getDisplayName() {
            StringBuilder res = new StringBuilder();
            if (internal) {
                res.append("Internal SD card");
            } else if (display_number > 1) {
                res.append("SD card " + display_number);
            } else {
                res.append("SD card");
            }
            if (readonly) {
                res.append(" (Read only)");
            }
            return res.toString();
        }
    }

    public static List<StorageInfo> getStorageList() {

        List<StorageInfo> list = new ArrayList<StorageInfo>();
        String def_path = Environment.getExternalStorageDirectory().getPath();
        boolean def_path_internal = !Environment.isExternalStorageRemovable();
        String def_path_state = Environment.getExternalStorageState();
        boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
                                    || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        BufferedReader buf_reader = null;
        try {
            HashSet<String> paths = new HashSet<String>();
            buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
            String line;
            int cur_display_number = 1;
            Log.d(TAG, "/proc/mounts");
            while ((line = buf_reader.readLine()) != null) {
                Log.d(TAG, line);
                if (line.contains("vfat") || line.contains("/mnt")) {
                    StringTokenizer tokens = new StringTokenizer(line, " ");
                    String unused = tokens.nextToken(); //device
                    String mount_point = tokens.nextToken(); //mount point
                    if (paths.contains(mount_point)) {
                        continue;
                    }
                    unused = tokens.nextToken(); //file system
                    List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
                    boolean readonly = flags.contains("ro");

                    if (mount_point.equals(def_path)) {
                        paths.add(def_path);
                        list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
                    } else if (line.contains("/dev/block/vold")) {
                        if (!line.contains("/mnt/secure")
                            && !line.contains("/mnt/asec")
                            && !line.contains("/mnt/obb")
                            && !line.contains("/dev/mapper")
                            && !line.contains("tmpfs")) {
                            paths.add(mount_point);
                            list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++));
                        }
                    }
                }
            }

            if (!paths.contains(def_path) && def_path_available) {
                list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
            }

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }    
}
Tepefy answered 14/11, 2013 at 16:3 Comment(5)
Thank's. Worked perfectly. And I like the way you made StorageInfo non mutable. On the other hand printStackTrace ? When we have android.util.Log.e ?Metempsychosis
Did NOT worked for USB devices attached through OTG cable on Nexus 5 and Nexus 7.Drye
I cannot use this to write file on SDCardHypozeuxis
same issue as @EuVid works on VM/AVD but not on hardwareLaggard
my workable case is to add one change to external SDCard path as below: path = path.replace("mnt/media_rw","storage");Schoolroom
D
11

It is possible to find where any additional SD cards are mounted by reading /proc/mounts (standard Linux file) and cross-checking against vold data (/system/etc/vold.conf). And note, that the location returned by Environment.getExternalStorageDirectory() may not appear in vold configuration (in some devices it's internal storage that cannot be unmounted), but still has to be included in the list. However we didn't find a good way to describe them to the user.

Deianira answered 16/11, 2011 at 13:24 Comment(4)
Imo, use of mount is more compatible than reading /proc filesystem. The problem is that SD card is not necessary formatted as FAT. Also, card mount point may vary from ROM to ROM. Also, there could be several others VFAT partitions...Janellajanelle
@borisstr: Hm, actually Android uses vold, so looking at it's config as well is appropriate.Deianira
The code file I shared from my post above includes a method to describe the discovered root paths to the user. Look at the setProperties() method.Sydel
@borisstr, actually, no, reading /proc/mounts is more portable on Android devices than launching the mount executable, especially as launching executables is discouraged.Evette
T
8

I try all solutions inside this topic on this time. But all of them did not work correctly on devices with one external (removable) and one internal (not-removable) cards. Path of external card not possible get from 'mount' command, from 'proc/mounts' file etc.

And I create my own solution (on Paulo Luan's):

String sSDpath = null;
File   fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard",  "externalSdCard")) // external sdcard
{
   fileCur = new File( "/mnt/", sPathCur);
   if( fileCur.isDirectory() && fileCur.canWrite())
   {
     sSDpath = fileCur.getAbsolutePath();
     break;
   }
}
fileCur = null;
if( sSDpath == null)  sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
Tortoni answered 23/9, 2014 at 11:24 Comment(0)
M
6

If you look at the source code for android.os.Environment you will see that Android relies heavily on environment variables for paths. You can use the "SECONDARY_STORAGE" environment variable to find the path to the removable sd card.

/**
 * Get a file using an environmental variable.
 *
 * @param variableName
 *         The Environment variable name.
 * @param paths
 *         Any paths to the file if the Environment variable was not found.
 * @return the File or {@code null} if the File could not be located.
 */
private static File getDirectory(String variableName, String... paths) {
    String path = System.getenv(variableName);
    if (!TextUtils.isEmpty(path)) {
        if (path.contains(":")) {
            for (String _path : path.split(":")) {
                File file = new File(_path);
                if (file.exists()) {
                    return file;
                }
            }
        } else {
            File file = new File(path);
            if (file.exists()) {
                return file;
            }
        }
    }
    if (paths != null && paths.length > 0) {
        for (String _path : paths) {
            File file = new File(_path);
            if (file.exists()) {
                return file;
            }
        }
    }
    return null;
}

Example usage:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
Muns answered 20/6, 2014 at 22:46 Comment(0)
M
5

Just simply use this:

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)
Meijer answered 8/8, 2014 at 5:23 Comment(2)
On some devices SECONDARY_STORAGE has several paths separated with a colon (":"). This is why I split the String (see my answer above).Muns
These both return null for me.Charlsiecharlton
R
5

Is there an universal way to find the location of an external SD card?

By universal way, if you mean official way; yes there is one.

In API level 19 i.e. in Android version 4.4 Kitkat, they have added File[] getExternalFilesDirs (String type) in Context Class that allows apps to store data/files in micro SD cards.

Android 4.4 is the first release of the platform that has actually allowed apps to use SD cards for storage. Any access to SD cards before API level 19 was through private, unsupported APIs.

getExternalFilesDirs(String type) returns absolute paths to application-specific directories on all shared/external storage devices. It means, it will return paths to both internal and external memory. Generally, second returned path would be the storage path for microSD card (if any).

But note that,

Shared storage may not always be available, since removable media can be ejected by the user. Media state can be checked using getExternalStorageState(File).

There is no security enforced with these files. For example, any application holding WRITE_EXTERNAL_STORAGE can write to these files.

The Internal and External Storage terminology according to Google/official Android docs is quite different from what we think.

Roan answered 3/8, 2016 at 17:1 Comment(2)
"The Internal and External Storage terminology according to Google/official Android docs is quite different from what we think." Yes, in fact the title of the question clarifies that the OP is asking about a removable SD card. getExternalFilesDirs() often returns SD cards that are not removable, so no, this is not a universal way to find the location of a removable SD card.Meneses
"getExternalFilesDirs(String type) returns absolute paths to application-specific directories on all shared/external storage devices. It means, it will return paths to both internal and external memory." This pair of sentences is very misleading, because in order for them to both be true, "external" has to mean two different and conflicting things.Meneses
B
4

Here is the way I use to find the external card. Use mount cmd return then parse the vfat part.

String s = "";
try {
Process process = new ProcessBuilder().command("mount")
        .redirectErrorStream(true).start();

process.waitFor();

InputStream is = process.getInputStream();
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
    s = s + new String(buffer);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}

//用行分隔mount列表
String[] lines = s.split("\n");
for(int i=0; i<lines.length; i++) {
//如果行内有挂载路径且为vfat类型,说明可能是内置或者外置sd的挂载点
if(-1 != lines[i].indexOf(path[0]) && -1 != lines[i].indexOf("vfat")) {
    //再用空格分隔
    String[] blocks = lines[i].split("\\s");
    for(int j=0; j<blocks.length; j++) {
        //判断是否是挂载为vfat类型
        if(-1 != blocks[j].indexOf(path[0])) {
            //Test if it is the external sd card.
        }
    }
}
}
Bactericide answered 23/2, 2012 at 2:25 Comment(0)
G
4

This solution handles the fact that System.getenv("SECONDARY_STORAGE") is of no use with Marshmallow.

Tested and working on:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - Stock)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - Stock)
  • Samsung Galaxy S4 (Android 4.4 - Stock)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - Stock)

    /**
     * Returns all available external SD-Card roots in the system.
     *
     * @return paths to all available external SD-Card roots in the system.
     */
    public static String[] getStorageDirectories() {
        String [] storageDirectories;
        String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            List<String> results = new ArrayList<String>();
            File[] externalDirs = applicationContext.getExternalFilesDirs(null);
            for (File file : externalDirs) {
                String path = file.getPath().split("/Android")[0];
                if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file))
                        || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){
                    results.add(path);
                }
            }
            storageDirectories = results.toArray(new String[0]);
        }else{
            final Set<String> rv = new HashSet<String>();
    
            if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
                final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
                Collections.addAll(rv, rawSecondaryStorages);
            }
            storageDirectories = rv.toArray(new String[rv.size()]);
        }
        return storageDirectories;
    }
    
Gardie answered 7/9, 2016 at 14:8 Comment(0)
S
2

Since my original answer above, scanning vold is no longer viable across the various manufacturers.

I've developed a more reliable and straight forward method.

File mnt = new File("/storage");
if (!mnt.exists())
    mnt = new File("/mnt");

File[] roots = mnt.listFiles(new FileFilter() {

    @Override
    public boolean accept(File pathname) {
        return pathname.isDirectory() && pathname.exists()
                && pathname.canWrite() && !pathname.isHidden()
                && !isSymlink(pathname);
    }
});

roots will contain all the writeable root directories on the system, including any usb connected usb devices.

NOTE: The canWrite method needs the android.permission.WRITE_EXTERNAL_STORAGE permission.

Sydel answered 17/3, 2015 at 18:52 Comment(4)
The method isSymlink(File) is undefined for the type new FileFilter(){}Erotic
Any idea if this will fail to find external sd cards on Android 4.4 due to canWrite?Savagism
This is certainly more straightforward than your other method, but is it reliable? E.g. I've read that on some Samsung devices, /external_sd is the external microSD card; on some LGs, it's /_ExternalSD; on some devices it's /sdcard. Maybe the latter is a symlink to /storage/sdcard0 or similar, but will these others really be reliably covered by /storage/* and /mount/*?Meneses
Also, is it necessary to use pathname.canWrite() and require WRITE_EXTERNAL_STORAGE permission? Why not just call pathname.canRead()?Meneses
M
2

Here is the method I use to find a removable SD card. It's complex, and probably overkill for some situations, but it works on a wide variety of Android versions and device manufacturers that I've tested over the last few years. I don't know of any devices since API level 15 on which it doesn't find the SD card, if there is one mounted. It won't return false positives in most cases, especially if you give it the name of a known file to look for.

Please let me know if you run into any cases where it doesn't work.

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.regex.Pattern;

public class SDCard {
    private static final String TAG = "SDCard";

    /** In some scenarios we can expect to find a specified file or folder on SD cards designed
     * to work with this app. If so, set KNOWNFILE to that filename. It will make our job easier.
     * Set it to null otherwise. */
    private static final String KNOWNFILE = null;

    /** Common paths for microSD card. **/
    private static String[] commonPaths = {
            // Some of these taken from
            // https://mcmap.net/q/128188/-removable-storage-external-sdcard-path-by-manufacturers
            // These are roughly in order such that the earlier ones, if they exist, are more sure
            // to be removable storage than the later ones.
            "/mnt/Removable/MicroSD",
            "/storage/removable/sdcard1", // !< Sony Xperia Z1
            "/Removable/MicroSD", // Asus ZenPad C
            "/removable/microsd",
            "/external_sd", // Samsung
            "/_ExternalSD", // some LGs
            "/storage/extSdCard", // later Samsung
            "/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
            "/mnt/extsd", // some Chinese tablets, e.g. Zeki
            "/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
            "/mnt/extSdCard",
            "/mnt/sdcard/external_sd",
            "/mnt/external_sd",
            "/storage/external_SD",
            "/storage/ext_sd", // HTC One Max
            "/mnt/sdcard/_ExternalSD",
            "/mnt/sdcard-ext",

            "/sdcard2", // HTC One M8s
            "/sdcard1", // Sony Xperia Z
            "/mnt/media_rw/sdcard1",   // 4.4.2 on CyanogenMod S3
            "/mnt/sdcard", // This can be built-in storage (non-removable).
            "/sdcard",
            "/storage/sdcard0",
            "/emmc",
            "/mnt/emmc",
            "/sdcard/sd",
            "/mnt/sdcard/bpemmctest",
            "/mnt/external1",
            "/data/sdext4",
            "/data/sdext3",
            "/data/sdext2",
            "/data/sdext",
            "/storage/microsd" //ASUS ZenFone 2

            // If we ever decide to support USB OTG storage, the following paths could be helpful:
            // An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
            // card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
            //        "/mnt/usb_storage",
            //        "/mnt/UsbDriveA",
            //        "/mnt/UsbDriveB",
    };

    /** Find path to removable SD card. */
    public static File findSdCardPath(Context context) {
        String[] mountFields;
        BufferedReader bufferedReader = null;
        String lineRead = null;

        /** Possible SD card paths */
        LinkedHashSet<File> candidatePaths = new LinkedHashSet<>();

        /** Build a list of candidate paths, roughly in order of preference. That way if
         * we can't definitively detect removable storage, we at least can pick a more likely
         * candidate. */

        // Could do: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists and has contents,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // I moved hard-coded paths toward the end, but we need to make sure we put the ones in
        // backwards order that are returned by the OS. And make sure the iterators respect
        // the order!
        // This is because when multiple "external" storage paths are returned, it's always (in
        // experience, but not guaranteed by documentation) with internal/emulated storage
        // first, removable storage second.

        // Add value of environment variables as candidates, if set:
        // EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
        // But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
        // And they are not documented (API) features. Typically useful only for old versions of Android.

        String val = System.getenv("SECONDARY_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
        val = System.getenv("EXTERNAL_SDCARD_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        // Get listing of mounted devices with their properties.
        ArrayList<File> mountedPaths = new ArrayList<>();
        try {
            // Note: Despite restricting some access to /proc (https://mcmap.net/q/128189/-file-system-changes-in-android-nougat),
            // Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
            bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));

            // Iterate over each line of the mounts listing.
            while ((lineRead = bufferedReader.readLine()) != null) {
                Log.d(TAG, "\nMounts line: " + lineRead);
                mountFields = lineRead.split(" ");

                // columns: device, mountpoint, fs type, options... Example:
                // /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
                String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];

                // The device, path, and fs type must conform to expected patterns.
                if (!(devicePattern.matcher(device).matches() &&
                        pathPattern.matcher(path).matches() &&
                        fsTypePattern.matcher(fsType).matches()) ||
                        // mtdblock is internal, I'm told.
                        device.contains("mtdblock") ||
                        // Check for disqualifying patterns in the path.
                        pathAntiPattern.matcher(path).matches()) {
                    // If this mounts line fails our tests, skip it.
                    continue;
                }

                // TODO maybe: check options to make sure it's mounted RW?
                // The answer at https://mcmap.net/q/128186/-how-can-i-get-the-external-sd-card-path-for-android-4-0 does.
                // But it hasn't seemed to be necessary so far in my testing.

                // This line met the criteria so far, so add it to candidate list.
                addPath(path, null, mountedPaths);
            }
        } catch (IOException ignored) {
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ignored) {
                }
            }
        }

        // Append the paths from mount table to candidate list, in reverse order.
        if (!mountedPaths.isEmpty()) {
            // See https://mcmap.net/q/45503/-convert-arraylist-lt-string-gt-to-string-array-duplicate on why the following is necessary.
            // Basically, .toArray() needs its parameter to know what type of array to return.
            File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
            addAncestors(candidatePaths, mountedPathsArray);
        }

        // Add hard-coded known common paths to candidate list:
        addStrings(candidatePaths, commonPaths);

        // If the above doesn't work we could try the following other options, but in my experience they
        // haven't added anything helpful yet.

        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.

        // This may be non-removable.
        Log.d(TAG, "Environment.getExternalStorageDirectory():");
        addPath(null, ancestor(Environment.getExternalStorageDirectory()), candidatePaths);

        // Context.getExternalFilesDirs() is only available from API level 19. You can use
        // ContextCompat.getExternalFilesDirs() on earlier APIs, but it only returns one dir anyway.
        Log.d(TAG, "context.getExternalFilesDir(null):");
        addPath(null, ancestor(context.getExternalFilesDir(null)), candidatePaths);

        // "Returns absolute paths to application-specific directories on all external storage
        // devices where the application can place persistent files it owns."
        // We might be able to use these to deduce a higher-level folder that isn't app-specific.
        // Also, we apparently have to call getExternalFilesDir[s](), at least in KITKAT+, in order to ensure that the
        // "external files" directory exists and is available.
        Log.d(TAG, "ContextCompat.getExternalFilesDirs(context, null):");
        addAncestors(candidatePaths, ContextCompat.getExternalFilesDirs(context, null));
        // Very similar results:
        Log.d(TAG, "ContextCompat.getExternalCacheDirs(context):");
        addAncestors(candidatePaths, ContextCompat.getExternalCacheDirs(context));

        // TODO maybe: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // A "public" external storage directory. But in my experience it doesn't add anything helpful.
        // Note that you can't pass null, or you'll get an NPE.
        final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        // Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
        addPath(null, publicDirectory.getParentFile(), candidatePaths);
        // EXTERNAL_STORAGE: may not be removable.
        val = System.getenv("EXTERNAL_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        if (candidatePaths.isEmpty()) {
            Log.w(TAG, "No removable microSD card found.");
            return null;
        } else {
            Log.i(TAG, "\nFound potential removable storage locations: " + candidatePaths);
        }

        // Accept or eliminate candidate paths if we can determine whether they're removable storage.
        // In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
        if (Build.VERSION.SDK_INT >= 21) {
            Iterator<File> itf = candidatePaths.iterator();
            while (itf.hasNext()) {
                File dir = itf.next();
                // handle illegalArgumentException if the path is not a valid storage device.
                try {
                    if (Environment.isExternalStorageRemovable(dir)
                        // && containsKnownFile(dir)
                            ) {
                        Log.i(TAG, dir.getPath() + " is removable external storage");
                        return dir;
                    } else if (Environment.isExternalStorageEmulated(dir)) {
                        Log.d(TAG, "Removing emulated external storage dir " + dir);
                        itf.remove();
                    }
                } catch (IllegalArgumentException e) {
                    Log.d(TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
                }
            }
        }

        // Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
        // On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
        if (Build.VERSION.SDK_INT >= 9) {
            File externalStorage = Environment.getExternalStorageDirectory();
            Log.d(TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
            if (Environment.isExternalStorageRemovable()) {
                // Make sure this is a candidate.
                // TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
                if (candidatePaths.contains(externalStorage)
                    // && containsKnownFile(externalStorage)
                        ) {
                    Log.d(TAG, "Using externalStorage dir " + externalStorage);
                    return externalStorage;
                }
            } else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
                Log.d(TAG, "Removing emulated external storage dir " + externalStorage);
                candidatePaths.remove(externalStorage);
            }
        }

        // If any directory contains our special test file, consider that the microSD card.
        if (KNOWNFILE != null) {
            for (File dir : candidatePaths) {
                Log.d(TAG, String.format(Locale.ROOT, "findSdCardPath: Looking for known file in candidate path, %s", dir));
                if (containsKnownFile(dir)) return dir;
            }
        }

        // If we don't find the known file, still try taking the first candidate.
        if (!candidatePaths.isEmpty()) {
            Log.d(TAG, "No definitive path to SD card; taking the first realistic candidate.");
            return candidatePaths.iterator().next();
        }

        // If no reasonable path was found, give up.
        return null;
    }

    /** Add each path to the collection. */
    private static void addStrings(LinkedHashSet<File> candidatePaths, String[] newPaths) {
        for (String path : newPaths) {
            addPath(path, null, candidatePaths);
        }
    }

    /** Add ancestor of each File to the collection. */
    private static void addAncestors(LinkedHashSet<File> candidatePaths, File[] files) {
        for (int i = files.length - 1; i >= 0; i--) {
            addPath(null, ancestor(files[i]), candidatePaths);
        }
    }

    /**
     * Add a new candidate directory path to our list, if it's not obviously wrong.
     * Supply path as either String or File object.
     * @param strNew path of directory to add (or null)
     * @param fileNew directory to add (or null)
     */
    private static void addPath(String strNew, File fileNew, Collection<File> paths) {
        // If one of the arguments is null, fill it in from the other.
        if (strNew == null) {
            if (fileNew == null) return;
            strNew = fileNew.getPath();
        } else if (fileNew == null) {
            fileNew = new File(strNew);
        }

        if (!paths.contains(fileNew) &&
                // Check for paths known not to be removable SD card.
                // The antipattern check can be redundant, depending on where this is called from.
                !pathAntiPattern.matcher(strNew).matches()) {

            // Eliminate candidate if not a directory or not fully accessible.
            if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
                Log.d(TAG, "  Adding candidate path " + strNew);
                paths.add(fileNew);
            } else {
                Log.d(TAG, String.format(Locale.ROOT, "  Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
                        strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
            }
        }
    }

    private static final String ANDROID_DIR = File.separator + "Android";

    private static File ancestor(File dir) {
        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.
        if (dir == null) {
            return null;
        } else {
            String path = dir.getAbsolutePath();
            int i = path.indexOf(ANDROID_DIR);
            if (i == -1) {
                return dir;
            } else {
                return new File(path.substring(0, i));
            }
        }
    }

    /** Returns true iff dir contains the special test file.
     * Assumes that dir exists and is a directory. (Is this a necessary assumption?) */
    private static boolean containsKnownFile(File dir) {
        if (KNOWNFILE == null) return false;

        File knownFile = new File(dir, KNOWNFILE);
        return knownFile.exists();
    }

    private static Pattern
            /** Pattern that SD card device should match */
            devicePattern = Pattern.compile("/dev/(block/.*vold.*|fuse)|/mnt/.*"),
    /** Pattern that SD card mount path should match */
    pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*",
            Pattern.CASE_INSENSITIVE),
    /** Pattern that the mount path should not match.
     * 'emulated' indicates an internal storage location, so skip it.
     * 'asec' is an encrypted package file, decrypted and mounted as a directory. */
    pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
    /** These are expected fs types, including vfat. tmpfs is not OK.
     * fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
    fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|fuse|sdcard|esdfs).*");
}

P.S.

  • Don't forget <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> in the manifest. And at API level 23 and higher, make sure to use checkSelfPermission / requestPermissions.
  • Set KNOWNFILE="myappfile" if there's a file or folder you expect to find on the SD card. That makes detection more accurate.
  • Obviously you'll want to cache the value of findSdCardPath(), rather than recomputing it every time you need it.
  • There's a bunch of logging (Log.d()) in the above code. It helps diagnose any cases where the right path isn't found. Comment it out if you don't want logging.
Meneses answered 28/1, 2019 at 22:46 Comment(1)
Downvoters, can you suggest a way for this answer to be improved?Meneses
W
1

it been so late but finally i got something i have tested most of devices( by manufacturer and android versions) its working on Android 2.2+. if you find it is not working, comment it with your device name. i will fix it. if anyone interested i will explain how it works.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;

import android.util.Log;


/**
 * @author ajeet
 *05-Dec-2014  2014
 *
 */
public class StorageUtil {

    public boolean isRemovebleSDCardMounted() {
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        boolean flag = false;
        for (File mmcfile : files) {
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    public String getRemovebleSDCardPath() throws IOException {
        String sdpath = null;
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        String sdcardDevfile = null;
        for (File mmcfile : files) {
            Log.d("SDCARD", mmcfile.getAbsolutePath());
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                sdcardDevfile = mmcfile.getName();
                Log.d("SDCARD", mmcfile.getName());
                break;
            }
        }
        if (sdcardDevfile == null) {
            return null;
        }
        FileInputStream is;
        BufferedReader reader;

        files = file.listFiles(new MmcblkFilter(sdcardDevfile + "p\\d+"));
        String deviceName = null;
        if (files.length > 0) {
            Log.d("SDCARD", files[0].getAbsolutePath());
            File devfile = new File(files[0], "dev");
            if (devfile.exists()) {
                FileInputStream fis = new FileInputStream(devfile);
                reader = new BufferedReader(new InputStreamReader(fis));
                String line = reader.readLine();
                deviceName = line;
            }
            Log.d("SDCARD", "" + deviceName);
            if (deviceName == null) {
                return null;
            }
            Log.d("SDCARD", deviceName);

            final File mountFile = new File("/proc/self/mountinfo");

            if (mountFile.exists()) {
                is = new FileInputStream(mountFile);
                reader = new BufferedReader(new InputStreamReader(is));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    // Log.d("SDCARD", line);
                    // line = reader.readLine();
                    // Log.d("SDCARD", line);
                    String[] mPonts = line.split("\\s+");
                    if (mPonts.length > 6) {
                        if (mPonts[2].trim().equalsIgnoreCase(deviceName)) {
                            if (mPonts[4].contains(".android_secure")
                                    || mPonts[4].contains("asec")) {
                                continue;
                            }
                            sdpath = mPonts[4];
                            Log.d("SDCARD", mPonts[4]);

                        }
                    }

                }
            }

        }

        return sdpath;
    }

    static class MmcblkFilter implements FilenameFilter {
        private String pattern;

        public MmcblkFilter(String pattern) {
            this.pattern = pattern;

        }

        @Override
        public boolean accept(File dir, String filename) {
            if (filename.matches(pattern)) {
                return true;
            }
            return false;
        }

    }

}
Wilkerson answered 9/12, 2014 at 7:1 Comment(14)
Hey downvoter please try first .if it is not working then comment your device. we using it over thousand of devices with Android 2.2+Wilkerson
Your class gives me /mnt/media_rw/extSdCard on Samsung Galaxy S4, GT-I9500, Android 5.0.1 (device is NOT rooted). But there is nothing visible in the folder /mnt/media_rw with ES File Manager...Tourneur
@Tourneur use if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.KITKAT) { File[] file = context.getExternalFilesDirs(null); return file.length>1?file[1]:null; }Wilkerson
What do you think about https://mcmap.net/q/128186/-how-can-i-get-the-external-sd-card-path-for-android-4-0 ? Is this approach more comprehensive for the case of Build.VERSION.SDK_INT >=Build.VERSION_CODES.KITKAT ?Tourneur
yes. it is guaranteed you get meaningful path from context.getExternalFilesDirs(null) , but you have trim it for root path(it will return path for you app directory. trim it on "/Android" )Wilkerson
I can't get SD card path on the Lenovo S939 Android 4.4.2 by means of your if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.KITKAT) { File[] file = context.getExternalFilesDirs(null); return file.length>1?file[1]:null; }Tourneur
@isasent what is output of context.getExternalFilesDirs(null)? output of "adb shell mount" ? output of "adb shell cat /proc/self/mountinfo" ? output of "adb shell df "Wilkerson
This Lenovo S939 is not in my hands physically, but I have two Linux configuration files - "/proc/self/mountinfo" dropbox.com/s/d4wdgql4qladmmt/mountinfo.txt?dl=0 and result of "adb shell mount" dropbox.com/s/nf5ng41p4r2ifoq/mount.txt?dl=0 executed on it.Tourneur
First attached file is a log of yours getRemovableSDCardPath which includes /proc/self/mountinfoTourneur
I have no the result of getExternalFilesDirs(null) right now, but I know that (file.length>1) is false.Tourneur
on your device code returns path /mnt/media_rw/sdcard0 that means there is sdcard present but this path is not accessible to app, but actual path is /storage/sdcard0 which mounted by /mnt/media_rw/sdcard0 . the problem is KITKAT and above getExternalFilesDirs(null) should return /storage/sdcard0 but it does not.( please verify on device /storage/sdcard0 is accessible )Wilkerson
The point is that /storage/sdcard0 is internal (non-removable) memory on this phone. Removable memory path is /storage/sdcard1 and ES-explorer finds it correctly... There are additional memory mount points /mnt/sdcard and /mnt/sdcard2. As I can understand /mnt/sdcard2 is a symlink to /storage/sdcard1Tourneur
There is a problem with your method for Lenovo a706 Row too with Android 4.1.2 (root). Files you need is here dropbox.com/s/c4ewc96ixytb7rn/…Tourneur
This is data you have asked for Lenovo S939 dropbox.com/s/o0j9e558cpoop9n/… (By the way - I'm not sure that user did not change phone configuration manually in case of Lenovo a706 Row, because the phone was rooted).Tourneur
K
1

The only working solution I found was this one that uses reflection

 /**
 * Get external sd card path using reflection
 * @param mContext
 * @param is_removable is external storage removable
 * @return
 */
private static String getExternalStoragePath(Context mContext, boolean is_removable) {

    StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
    Class<?> storageVolumeClazz = null;
    try {
        storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
        Object result = getVolumeList.invoke(mStorageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String path = (String) getPath.invoke(storageVolumeElement);
            boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
            if (is_removable == removable) {
                return path;
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}
Kylix answered 5/12, 2019 at 12:47 Comment(2)
I personally do not prefer using reflection because google does not appreciate backward compatibility in new versions of android!Meijer
As of API level 24, you do not need reflection: developer.android.com/reference/android/os/storage/…Sprag
J
1

Google had blocked many options to get the external sd card path and add permissions levels to protect the sd card directory from the app's garbage. Every solution I had tried didn't supply a sufficient way to get the external and removable sd card path.

After that said, Google did supply a way to get the external paths that the app can write to it and check if it is removable or not.

With this simple API, you can get the path to the removable external directory and with the right write/read permissions.

File[] files = getExternalFilesDirs(null);
for(File file : files){
    if(Environment.isExternalStorageRemovable(file)){
        return file;
    }
}
Jamisonjammal answered 24/3, 2021 at 10:20 Comment(1)
Thank you for this code snippet, which might provide some limited, immediate help. A proper explanation would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please edit your answer to add some explanation, including the assumptions you’ve made.Archiearchiepiscopacy
T
0

I don't know why but I need to call .createNewFile() on a File created in the public storage directories before using it. In the framework the comments for that method say it isn't useful. Here's a sample...


 String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) + File.separator + "My Directory";
            final File myDir = new File(myPath);
            try {
                myDir.mkdirs();
            } catch (Exception ex) {
                Toast.makeText(this, "error: " + ex.getMessage(), Toast.LENGTH_LONG).show();
            }

        String fname = "whatever";
        File newFile = new File(myDir, fname);

        Log.i(TAG, "File exists --> " + newFile.exists()) //will be false  
    try {
            if (newFile.createNewFile()) {

                 //continue 

              } else {

                Log.e(TAG, "error creating file");

            }

        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }

Typehigh answered 25/3, 2015 at 16:25 Comment(0)
T
0

I have created a utils method to check a SD card is available on device or not, and get SD card path on device if it available.

You can copy 2 methods bellow into your project's class that you need. That's all.

public String isRemovableSDCardAvailable() {
    final String FLAG = "mnt";
    final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
    final String EXTERNAL_STORAGE_DOCOMO = System.getenv("EXTERNAL_STORAGE_DOCOMO");
    final String EXTERNAL_SDCARD_STORAGE = System.getenv("EXTERNAL_SDCARD_STORAGE");
    final String EXTERNAL_SD_STORAGE = System.getenv("EXTERNAL_SD_STORAGE");
    final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");

    Map<Integer, String> listEnvironmentVariableStoreSDCardRootDirectory = new HashMap<Integer, String>();
    listEnvironmentVariableStoreSDCardRootDirectory.put(0, SECONDARY_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(1, EXTERNAL_STORAGE_DOCOMO);
    listEnvironmentVariableStoreSDCardRootDirectory.put(2, EXTERNAL_SDCARD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(3, EXTERNAL_SD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(4, EXTERNAL_STORAGE);

    File externalStorageList[] = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        externalStorageList = getContext().getExternalFilesDirs(null);
    }
    String directory = null;
    int size = listEnvironmentVariableStoreSDCardRootDirectory.size();
    for (int i = 0; i < size; i++) {
        if (externalStorageList != null && externalStorageList.length > 1 && externalStorageList[1] != null)
            directory = externalStorageList[1].getAbsolutePath();
        else
            directory = listEnvironmentVariableStoreSDCardRootDirectory.get(i);

        directory = canCreateFile(directory);
        if (directory != null && directory.length() != 0) {
            if (i == size - 1) {
                if (directory.contains(FLAG)) {
                    Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
                    return directory;
                } else {
                    return null;
                }
            }
            Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
            return directory;
        }
    }
    return null;
}

/**
 * Check if can create file on given directory. Use this enclose with method
 * {@link BeginScreenFragement#isRemovableSDCardAvailable()} to check sd
 * card is available on device or not.
 * 
 * @param directory
 * @return
 */
public String canCreateFile(String directory) {
    final String FILE_DIR = directory + File.separator + "hoang.txt";
    File tempFlie = null;
    try {
        tempFlie = new File(FILE_DIR);
        FileOutputStream fos = new FileOutputStream(tempFlie);
        fos.write(new byte[1024]);
        fos.flush();
        fos.close();
        Log.e(getClass().getSimpleName(), "Can write file on this directory: " + FILE_DIR);
    } catch (Exception e) {
        Log.e(getClass().getSimpleName(), "Write file error: " + e.getMessage());
        return null;
    } finally {
        if (tempFlie != null && tempFlie.exists() && tempFlie.isFile()) {
            // tempFlie.delete();
            tempFlie = null;
        }
    }
    return directory;
}
Taskwork answered 30/11, 2015 at 7:12 Comment(0)
S
0

By writing below code you will get the location:

/storage/663D-554E/Android/data/app_package_name/files/

which stores your app data at /android/data location inside the sd_card.

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

list[1]+"/fol" 

for getting location pass 0 for internal and 1 for sdcard to file array.

I have tested this code on a moto g4 plus and Samsung device (all works fine).

hope this might helpful.

Scolecite answered 24/1, 2018 at 12:1 Comment(1)
sometimes sd card path is not on index 1, I have seen the cases where it was on index 0. better to follow something elseSestertium
E
0

This is the conclusion of all reliable methods, to work for all android versions (Hopefully).

private static ArrayList<String> getAllStorages(Context context)
{
    ArrayList<String> storagePaths = new ArrayList<>();

    storagePaths.add(Environment.getExternalStorageDirectory().getAbsolutePath());

    try
    {
        File[] externalDirs = context.getExternalFilesDirs(null);
        if (externalDirs != null)
            for (File file : externalDirs)
            {
                storagePaths.add(file.getAbsolutePath());
            }
        
        externalDirs = context.getExternalMediaDirs();
        if (externalDirs != null)
            for (File file : externalDirs)
            {
                storagePaths.add(file.getAbsolutePath());
            }
    }
    catch (Throwable e)
    {
        // ignore
    }

    try
    {
        StorageManager sm=(StorageManager) context.getSystemService(context.STORAGE_SERVICE);
        for (StorageVolume e:sm.getStorageVolumes())
        {
            String s= (String) StorageVolume.class.getMethod("getPath").invoke(e);
            storagePaths.add(s);
        }
    }
    catch (Throwable e)
    {
        //ignore
    }
    
    for (String key: new String[]{
        "SECONDARY_STORAGE",
        "EXTERNAL_STORAGE",
        "EMULATED_STORAGE_TARGET",
        "EXTERNAL_STORAGE_DOCOMO",
        "EXTERNAL_SDCARD_STORAGE",
        "EXTERNAL_SD_STORAGE",
        // add more if you have :-D
    })
    {
        String externalStorage = System.getenv(key);
        if (externalStorage != null && !externalStorage.isEmpty())
        {
            String[] externalPaths = externalStorage.split(":");
            for (String e:externalPaths)
            {
                storagePaths.add(e);
            }
        }
    }

    ArrayList<String> result=new ArrayList<>();
    for (String s:storagePaths)
    {
        File f=new File(s);
        File f2=f;
        while (f2 != null)
        {
            if (f2.canRead())
            {
                f = f2;
            }
            f2 = f2.getParentFile();
        }
        try
        {
            f = f.getCanonicalFile();
        }
        catch (IOException e)
        {
            // ignore
        }
        s = f.getPath();
        if (!result.contains(s))
        {
            result.add(s);
        }
    }
    return result;
}

Tested on Android 11. You should test it on each version and comment below if any is not compatible, so can improve it.

Exarch answered 18/9, 2023 at 16:39 Comment(0)
S
-1

Its work for all external devices, But make sure only get external device folder name and then you need to get file from given location using File class.

public static List<String> getExternalMounts() {
        final List<String> out = new ArrayList<>();
        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;
    }

Calling:

List<String> list=getExternalMounts();
        if(list.size()>0)
        {
            String[] arr=list.get(0).split("/");
            int size=0;
            if(arr!=null && arr.length>0) {
                size= arr.length - 1;
            }
            File parentDir=new File("/storage/"+arr[size]);
            if(parentDir.listFiles()!=null){
                File parent[] = parentDir.listFiles();

                for (int i = 0; i < parent.length; i++) {

                    // get file path as parent[i].getAbsolutePath());

                }
            }
        }

Getting access to external storage

In order to read or write files on the external storage, your app must acquire the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE system permissions. For example:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>
Semiyearly answered 13/8, 2016 at 7:51 Comment(0)
M
-1

This is how I find path of external storage.

public static String getExternalStoragePath(Context context){
        File[] f = ContextCompat.getExternalFilesDirs(context, null);
        String internalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String excessive = "";
        String externalPath = "";
        for (File file : f) {
            if (file.getAbsolutePath().startsWith(internalPath)) {
                excessive = file.getAbsolutePath().replaceAll(internalPath, "");
            }
        }
        for (File file : f) {
            if (!file.getAbsolutePath().startsWith(excessive)) {
                externalPath = file.getAbsolutePath().replaceAll(excessive, "");
            }
        }
        return  externalPath;
    }
Monocyclic answered 6/9, 2022 at 18:43 Comment(0)
B
-2

/sdcard => Internal Storage (It's a symlink but should work)

/mnt/extSdCard => External Sdcard

This is for Samsung Galaxy S3

You can probably bank on this being true for most...double check however!

Burschenschaft answered 15/7, 2013 at 3:11 Comment(3)
I've had several different Android phones, about half of them Samsung, and haven't ever seen this location used. It might be true on the S3, but saying "you can probably bank on this being true for most" is completely wrong.Esbensen
wrong. /sdcard is a symlink to external on my sony 2305.Endocranium
Didn't I say that it might not be?Burschenschaft

© 2022 - 2024 — McMap. All rights reserved.