I have an app with huge data pre-stored in the SD Card.
We are supporting all tablets ICS onwards. I am not able to find a way to access the SDCard location correctly on ALL devices.
I looked at various solutions given here on SO but they do not seem to work in all cases. Looking for a generic solution.
Even if anyone can tell me all the possible mount points of SDCard.
I am targeting android tablets only if that can narrow down the solution.
Unfortunately this is a common problem due to the fact that Android devices are highly fragmented. Environment.getExternalStorageDirectory() refers to whatever the device manufacturer considers 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.(http://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()) Here, "external storage" means "the drive accessible via USB Mass Storage mode when mounted on a host machine".
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. For most conforming android devices(known ones from google compliance list) the Environment.getExternalStorageDirectory() should work. Or you could write a custom storage class that looks at the mount points and gives you the right path to the mounted SDCard. This is something I've implemented and it has worked so far.
public class StorageOptions {
private static ArrayList<String> mMounts = new ArrayList<String>();
private static ArrayList<String> mVold = new ArrayList<String>();
public static String[] labels;
public static String[] paths;
public static int count = 0;
private static final String TAG = StorageOptions.class.getSimpleName();
public static void determineStorageOptions() {
readMountsFile();
readVoldFile();
compareMountsWithVold();
testAndCleanMountsList();
setProperties();
}
private static void readMountsFile() {
/*
* Scan the /proc/mounts file and look for lines like this:
* /dev/block/vold/179:1 /mnt/sdcard vfat
* rw,dirsync,nosuid,nodev,noexec,
* relatime,uid=1000,gid=1015,fmask=0602,dmask
* =0602,allow_utime=0020,codepage
* =cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
*
* When one is found, split it into its elements and then pull out the
* path to the that mount point and add it to the arraylist
*/
// some mount files don't list the default
// path first, so we add it here to
// ensure that it is first in our list
mMounts.add("/mnt/sdcard");
try {
Scanner scanner = new Scanner(new File("/proc/mounts"));
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) {
// Auto-generated catch block
e.printStackTrace();
}
}
private static void readVoldFile() {
/*
* Scan the /system/etc/vold.fstab file and look for lines like this:
* dev_mount sdcard /mnt/sdcard 1
* /devices/platform/s3c-sdhci.0/mmc_host/mmc0
*
* When one is found, split it into its elements and then pull out the
* path to the that mount point and add it to the arraylist
*/
// some devices are missing the vold file entirely
// so we add a path here to make sure the list always
// includes the path to the first sdcard, whether real
// or emulated.
mVold.add("/mnt/sdcard");
try {
Scanner scanner = new Scanner(new File("/system/etc/vold.fstab"));
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(":"));
// don't add the default vold path
// it's already in the list.
if (!element.equals("/mnt/sdcard"))
mVold.add(element);
}
}
} catch (Exception e) {
// Auto-generated catch block
e.printStackTrace();
}
}
private static void compareMountsWithVold() {
/*
* Sometimes the two lists of mount points will be different. We only
* want those mount points that are in both list.
*
* Compare the two lists together and remove items that are not in both
* lists.
*/
for (int i = 0; i < mMounts.size(); i++) {
String mount = mMounts.get(i);
if (!mVold.contains(mount))
mMounts.remove(i--);
}
// don't need this anymore, clear the vold list to reduce memory
// use and to prepare it for the next time it's needed.
mVold.clear();
}
private static void testAndCleanMountsList() {
/*
* Now that we have a cleaned list of mount paths Test each one to make
* sure it's a valid and available path. If it is not, remove it from
* the list.
*/
for (int i = 0; i < mMounts.size(); i++) {
String mount = mMounts.get(i);
File root = new File(mount);
if (!root.exists() || !root.isDirectory() || !root.canWrite())
mMounts.remove(i--);
}
}
#SuppressWarnings("unchecked")
private static void setProperties() {
/*
* At this point all the paths in the list should be valid. Build the
* public properties.
*/
Constants.mMounts = new ArrayList<String>();
ArrayList<String> mLabels = new ArrayList<String>();
int j = 0;
if (mMounts.size() > 0) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD)
mLabels.add("Auto");
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
if (Environment.isExternalStorageRemovable()) {
mLabels.add("External SD Card 1");
j = 1;
} else
mLabels.add("Internal Storage");
} else {
if (!Environment.isExternalStorageRemovable()
|| Environment.isExternalStorageEmulated())
mLabels.add("Internal Storage");
else {
mLabels.add("External SD Card 1");
j = 1;
}
}
if (mMounts.size() > 1) {
for (int i = 1; i < mMounts.size(); i++) {
mLabels.add("External SD Card " + (i + j));
}
}
}
labels = new String[mLabels.size()];
mLabels.toArray(labels);
paths = new String[mMounts.size()];
mMounts.toArray(paths);
Constants.mMounts = (ArrayList<String>) mMounts.clone();
Constants.mLabels = (ArrayList<String>) mLabels.clone();
count = Math.min(labels.length, paths.length);
// don't need this anymore, clear the mounts list to reduce memory
// use and to prepare it for the next time it's needed.
mMounts.clear();
}
}
I found this off of a similar question from SO, that I dont have the link to unfortunately, but it s there on probably Sony's Android developer site(no links there either unfortunately). Wagic - a C++ game engine library implements the same and their code is here: http://wagic.googlecode.com/svn-history/r4300/trunk/projects/mtg/Android/src/net/wagic/utils/StorageOptions.java
so you could look at the implementation. I wish someone from Google can answer this question and provide a single way that reads the SDcard mount point from all Android devices
Its not simple as you are thinking,
String f = Environment.getExternalStorageDirectory().getAbsolutePath();
Log.v("TAG",f);//to print the path of sdcard
It returns device storage path not an external sdcard path.
If you are trying to get the path to the SD card use this code
String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
Then use this to get the path of the specific folder/file
String path = baseDir + "/your folder(s)/" + fileName;
Its simple.
String f = Environment.getExternalStorageDirectory().getAbsolutePath();
Log.v("TAG",f);//to print the path of sdcard
if you want to access a file then.
String f = Environment.getExternalStorageDirectory()+"/file.ext";
Log.v("TAG",f);//to print the path of file in Logcat.
Related
When I plug my device into big computer I see the following picture
How to find these (two) directories programmatically from withing Android application?
UPDATE
I wrote utility class to deduce roots. Unfortunately, it works for minSdkVersion=19
public class RootsUtil {
private final static String seed = Environment.DIRECTORY_PICTURES;
public final static File[] getRoots(Context context) {
File[] paths = context.getExternalFilesDirs(seed);
if( paths.length <= 1 ) {
return new File[] { Environment.getExternalStorageDirectory() };
}
else {
while(true) {
int count = 1;
for (int i = 1; i < paths.length; ++i) {
if (paths[0].getName().equals(paths[i].getName())) {
count++;
}
}
if( count==paths.length ) {
for (int i = 0; i < paths.length; ++i) {
paths[i] = paths[i].getParentFile();
}
}
else {
break;
}
}
return paths;
}
}
}
The question persists: are there any solutions for at least SDK=15?
P.S.
People downvoting this (absolutely normal) question: you are just declaring yourselves a clowns.
How to find these (two) directories programmatically from withing Android application?
You don't.
The one labeled "Phone" presumably is what the Android SDK refers to as external storage. I say "presumably" because device manufacturers seem to change this label — I usually see it called "Internal" or "Internal storage". To get the root of external storage, use Environment.getExternalStorageDirectory(). Note that this requires that you hold the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permissions, which includes asking for those permissions at runtime.
The one labeled "Card" presumably is referring to some removable media. You cannot work with the root directory of removable storage.
Maybe this will help you:
https://stackoverflow.com/a/40123073/5002496
thanks to that method you can list all mounted external storages paths. I am using it in my project to store data in sd-card and tested it on more than 20 devices.
Is there a Java equivalent for System.IO.Path.Combine() in C#/.NET? Or any code to accomplish this?
This static method combines one or more strings into a path.
Rather than keeping everything string-based, you should use a class which is designed to represent a file system path.
If you're using Java 7 or Java 8, you should strongly consider using java.nio.file.Path; Path.resolve can be used to combine one path with another, or with a string. The Paths helper class is useful too. For example:
Path path = Paths.get("foo", "bar", "baz.txt");
If you need to cater for pre-Java-7 environments, you can use java.io.File, like this:
File baseDirectory = new File("foo");
File subDirectory = new File(baseDirectory, "bar");
File fileInDirectory = new File(subDirectory, "baz.txt");
If you want it back as a string later, you can call getPath(). Indeed, if you really wanted to mimic Path.Combine, you could just write something like:
public static String combine(String path1, String path2)
{
File file1 = new File(path1);
File file2 = new File(file1, path2);
return file2.getPath();
}
In Java 7, you should use resolve:
Path newPath = path.resolve(childPath);
While the NIO2 Path class may seem a bit redundant to File with an unnecessarily different API, it is in fact subtly more elegant and robust.
Note that Paths.get() (as suggested by someone else) doesn't have an overload taking a Path, and doing Paths.get(path.toString(), childPath) is NOT the same thing as resolve(). From the Paths.get() docs:
Note that while this method is very convenient, using it will imply an assumed reference to the default FileSystem and limit the utility of the calling code. Hence it should not be used in library code intended for flexible reuse. A more flexible alternative is to use an existing Path instance as an anchor, such as:
Path dir = ...
Path path = dir.resolve("file");
The sister function to resolve is the excellent relativize:
Path childPath = path.relativize(newPath);
The main answer is to use File objects. However Commons IO does have a class FilenameUtils that can do this kind of thing, such as the concat() method.
platform independent approach (uses File.separator, ie will works depends on operation system where code is running:
java.nio.file.Paths.get(".", "path", "to", "file.txt")
// relative unix path: ./path/to/file.txt
// relative windows path: .\path\to\filee.txt
java.nio.file.Paths.get("/", "path", "to", "file.txt")
// absolute unix path: /path/to/filee.txt
// windows network drive path: \\path\to\file.txt
java.nio.file.Paths.get("C:", "path", "to", "file.txt")
// absolute windows path: C:\path\to\file.txt
I know its a long time since Jon's original answer, but I had a similar requirement to the OP.
By way of extending Jon's solution I came up with the following, which will take one or more path segments takes as many path segments that you can throw at it.
Usage
Path.combine("/Users/beardtwizzle/");
Path.combine("/", "Users", "beardtwizzle");
Path.combine(new String[] { "/", "Users", "beardtwizzle", "arrayUsage" });
Code here for others with a similar problem
public class Path {
public static String combine(String... paths)
{
File file = new File(paths[0]);
for (int i = 1; i < paths.length ; i++) {
file = new File(file, paths[i]);
}
return file.getPath();
}
}
To enhance JodaStephen's answer, Apache Commons IO has FilenameUtils which does this. Example (on Linux):
assert org.apache.commons.io.FilenameUtils.concat("/home/bob", "work\\stuff.log") == "/home/bob/work/stuff.log"
It's platform independent and will produce whatever separators your system needs.
Late to the party perhaps, but I wanted to share my take on this. I prefer not to pull in entire libraries for something like this. Instead, I'm using a Builder pattern and allow conveniently chained append(more) calls. It even allows mixing File and String, and can easily be extended to support Path as well. Furthermore, it automatically handles the different path separators correctly on both Linux, Macintosh, etc.
public class Files {
public static class PathBuilder {
private File file;
private PathBuilder ( File root ) {
file = root;
}
private PathBuilder ( String root ) {
file = new File(root);
}
public PathBuilder append ( File more ) {
file = new File(file, more.getPath()) );
return this;
}
public PathBuilder append ( String more ) {
file = new File(file, more);
return this;
}
public File buildFile () {
return file;
}
}
public static PathBuilder buildPath ( File root ) {
return new PathBuilder(root);
}
public static PathBuilder buildPath ( String root ) {
return new PathBuilder(root);
}
}
Example of usage:
File root = File.listRoots()[0];
String hello = "hello";
String world = "world";
String filename = "warez.lha";
File file = Files.buildPath(root).append(hello).append(world)
.append(filename).buildFile();
String absolute = file.getAbsolutePath();
The resulting absolute will contain something like:
/hello/world/warez.lha
or maybe even:
A:\hello\world\warez.lha
If you do not need more than strings, you can use com.google.common.io.Files
Files.simplifyPath("some/prefix/with//extra///slashes" + "file//name")
to get
"some/prefix/with/extra/slashes/file/name"
Here's a solution which handles multiple path parts and edge conditions:
public static String combinePaths(String ... paths)
{
if ( paths.length == 0)
{
return "";
}
File combined = new File(paths[0]);
int i = 1;
while ( i < paths.length)
{
combined = new File(combined, paths[i]);
++i;
}
return combined.getPath();
}
This also works in Java 8 :
Path file = Paths.get("Some path");
file = Paths.get(file + "Some other path");
This solution offers an interface for joining path fragments from a String[] array. It uses java.io.File.File(String parent, String child):
public static joinPaths(String[] fragments) {
String emptyPath = "";
return buildPath(emptyPath, fragments);
}
private static buildPath(String path, String[] fragments) {
if (path == null || path.isEmpty()) {
path = "";
}
if (fragments == null || fragments.length == 0) {
return "";
}
int pathCurrentSize = path.split("/").length;
int fragmentsLen = fragments.length;
if (pathCurrentSize <= fragmentsLen) {
String newPath = new File(path, fragments[pathCurrentSize - 1]).toString();
path = buildPath(newPath, fragments);
}
return path;
}
Then you can just do:
String[] fragments = {"dir", "anotherDir/", "/filename.txt"};
String path = joinPaths(fragments);
Returns:
"/dir/anotherDir/filename.txt"
Assuming all given paths are absolute paths. you can follow below snippets to merge these paths.
String baseURL = "\\\\host\\testdir\\";
String absoluteFilePath = "\\\\host\\testdir\\Test.txt";;
String mergedPath = Paths.get(baseURL, absoluteFilePath.replaceAll(Matcher.quoteReplacement(baseURL), "")).toString();
output path is \\host\testdir\Test.txt.
In Android SDK we can detect External sd card with,
Environment.isExternalStorageRemovable();
or
Environment.isExternalStorageEmulated();
I want to know if it is possible with Adobe Air Android SDK (3.2 or above)?
Thanks
I do not believe there is an equivalent method with the AIR SDK. However it is possible to develop "AIR Native Extensions (ANEs)" to give you access to methods available in the native environment. You can find many tutorials on the net, but I recommend starting here
I don't know if this might work on Android, but anyway
import flash.filesystem.StorageVolume;
import flash.filesystem.StorageVolumeInfo;
var volumes = StorageVolumeInfo.storageVolumeInfo.getStorageVolumes();
for (var i = 0; i < volumes.length; i++)
{
var volume:StorageVolume = volumes[i];
trace ("isRemovable :" + volume.isRemovable);
}
Though can't remember somethin like this,
Environment.isExternalStorageEmulated();
IMHO you might need ANE anyway.
This should work:
isExternalStorageRemovable()
public static boolean isExternalStorageRemovable() {
String val = System.getenv("SECONDARY_STORAGE");
if (val == null) {
return false;
}
File microSd = null;
String[] arr = val.split(":");
for (String path : arr) {
File file = new File(path);
if (file.isDirectory()) {
microSd = file;
break;
}
}
return microSd != null;
}
isExternalStorageEmulated()
public static boolean isExternalStroageEmulated() {
return System.getEnv("EMULATED_STORAGE_SOURCE") != null;
}
The closest thing to documentation I can find having to do with file storage is this post (see below if you can't access it), but it leaves me with several questions.
I would really, really, really like a knowledgeable explanation of what paths map to what storage here, seeing as how we're supposed to hard-code them, and how precisely we're expected to access them. An actual code sample would be superlative. My best guess from messing around with this is that:
/sdcard-> maps to the internal eMMC slot, and access is restricted.
Environment.getExternalStorageDirectory(); ... still returns this.
/media -> maps to the internal 8GB memory (I can write to this)
/data -> ?
? -> maps to the optional microSD card
How can we access the external (optional, additional, the one you can pop out) sdcard, if /sdcard maps to restricted storage instead?
Now to quote the Nook developer docs:
Background There are two different partition schemes for the NOOK
Color devices in market today, one with only 250MB available to
applications on the /data partition and one with 4GB available to
applications on the /data partition. As a result, it is imperative
that applications are designed and developed in such a way as to
manage space effectively. Applications which fail to do so will not be
accepted for distribution via the Shop.
Area Associated Technical Recommendation or Solution if your
application requires large amount of data (including but not limited
to images, audio or video content), you should download those
resources at runtime and store them in the larger partition of the
device. If your application is going to request and store more than
100MB of data or resource you MUST abide by the the following
restrictions:
Your application must clearly and explicitly state in the description
provided that a large amount of data is used/delivered by the
application. You MUST write your resources and data onto appropriate
partition. You can detect if the device has an enlarged /data
partition as follows :
StatFs stat = new StatFs("/data");
long bytesAvailable = (long)stat.getBlockSize() *(long)stat.getBlockCount();
long megAvailable = bytesAvailable / 1048576;
if (megAvailable > 1000){
... write your resources in /data
} else {
... write your resources on /mnt/media ...
}
To write data into your application's private space on /data
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_WORLD_READABLE);
Your application should NOT assume the
presence of an sdcard on device, but you can test for one via a call
to
Environment.getExternalStorageState(); If an SD Card is not found,
your application MUST exit gracefully with a notification to the user
as to the reason for the exit.
Remember, that to access the /media partition, as well as
ExternalStorage you need to declare in your manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
Okay, here's what I've learned in the past couple of weeks.
If you want to write to the internal SDcard, use Context.getFilesDir(). It'll return the private directory for your application. You can not invent your own directories on the internal flash storage (aka "/data"). You don't have permission to write anywhere other than the folder your application gets assigned. Supposedly there are two internal partitions, "/data" and "/media", but I can't get at "/media" to save my life.
You can use the external flash memory, "/sdcard", when one is available. This is the card you can pop out of the device. There are two ways to go about this:
Store things in the folder assigned to your app (so it'll get deleted
when your application is uninstalled). You can find that folder with
Context.getExternalFilesDir().
Store things wherever, either in some hard-coded path under "/sdcard/foo/bar" or in
Environment.getExternalStorageDirectory() / whatever.
This post by a B&N rep (which I referenced in my question) turned out to be a bit of a red herring, "/sdcard" doesn't map to the eMMC slot, and I have no idea what "we mapped the SD card to our internal eMMC" means.
This B&N post says that "/media" is internal, but I can't write to it even though I have the proper manifest permissions... so go figure.
This is a screencap of my test device, showing what is and isn't accessible:
The code for that (note that FileUtils isn't included in the sdk by default,it's from the org.apache.commons.io lib):
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView dataView = (TextView) findViewById(R.id.data);
dataView.setText(testIt("/data"));
TextView mediaView = (TextView) findViewById(R.id.media);
mediaView.setText(testIt("/media"));
TextView mntMediaView = (TextView) findViewById(R.id.mntMedia);
mntMediaView.setText(testIt("/mnt/media"));
try {
File fd = this.getFilesDir();
if(fd != null) {
TextView fdView = (TextView) findViewById(R.id.filesDir);
fdView.setText("getFilesDir(): " + testIt(fd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File efd = this.getExternalFilesDir(null);
if(efd != null) {
TextView efdView = (TextView) findViewById(R.id.externalFilesDir);
efdView.setText("getExternalFilesDir(): " + testIt(efd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File esd = Environment.getExternalStorageDirectory();
if(esd != null) {
TextView esdView = (TextView) findViewById(R.id.externalStorageDirectory);
esdView.setText("getExternalStorageDirectory(): " + testIt(esd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File espd = Environment.getExternalStoragePublicDirectory(null);
if(espd != null) {
TextView espdView = (TextView) findViewById(R.id.externalStoragePublicDirectory);
espdView.setText("getExternalStoragePublicDirectory(): " + testIt(espd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String testIt(String dir){
StatFs stat = new StatFs(dir);
long bytesAvailable = (long) stat.getBlockSize() * (long) stat.getBlockCount();
long megAvailable = bytesAvailable / FileUtils.ONE_MB;
File dirFile = new File(dir + "/test/");
dirFile.mkdir();
return dir + "/test \n canRead() " + dirFile.canRead() + ", \n canWrite() " + dirFile.canWrite() + " with " + megAvailable + "MB available";
}
First of all try the following methods:
Context.getExternalFilesDir
Context.getExternalCacheDir
Environment.getExternalStoragePublicDirectory
If neither of those return you a directory where you can write to, check the following google-groups thread, and use the code provided in the last answer, which enumerates all current mount-points:
I have the same issue with Galaxy S. Until now all Android devices I
got have "mount" command available. I build a list of available volume
parsing "mount" response. This is not perfect but cleaver than Android
storage API.
String cmd = "/system/bin/mount";
try {
Runtime rt = Runtime.getRuntime();
Process ps = rt.exec(cmd);
BufferedReader rd = new BufferedReader( new InputStreamReader(ps.getInputStream()) );
String rs;
while ((rs = rd.readLine()) != null)
{
//check what you need!
Log.i("MOUNT_CMD", rs);
}
rd.close();
ps.waitFor();
} catch(Exception e) {
//...
}
If it is possible to insert the microSD card in the Nook device, while it is running, you could also try the following:
mVolumeManagerReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.i("MediaMounter", "Storage: " + intent.getData());
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
filter.addDataScheme("file");
context.registerReceiver(mVolumeManagerReceiver, filter);
Some Android 2.x tablets such as the HTC Flyer and Samsung Galaxy Tab support both internal tablet storage and an external SD card. For example on my Flyer, /sdcard and /sdcard2 are separate, with the former representing the tablet's "internal storage."
If I use Environment.getExternalStorageDirectory(), there doesn't seem to be any set rule on which of those storage locations will be returned. In using getExternalStorageDirectory(), my concern is that I'll only find media stored on one of the two storage locations.
One solution is to just hard-code scanning /sdcard* into the application, but this makes an assumption that all devices will use that as a naming scheme, and I don't consider that a safe assumption.
Is there a documented way to scan and use multiple sd card/storage locations on Android 2.x?
My goal is actually to find all audiobooks on the tablet, so I'd like to find and use all /sdcard*/Audiobooks in some documented way.
Normally second sdcard is mounted within the first one.
Here is what I mean:
First sdcard is mounted at /mnt/sdcard.
The second sdcard is mounted into /mnt/sdcard/external_sd
So whenever you receive /mnt/sdcard as path for external storage and start scanning the tree, you won't miss the second sdcard (if it was also mounted).
This is not documented behavior. But Galaxy Tab behaves exactly this way. I assume this is because there is no official Android API support (i.e. no special function in Environment class) for multiple external storages.
You may use the code bellow to get paths to all SD-CARDs in system.
This works on all Android versions and return paths to all storages (include emulated).
Works correctly on all my devices.
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
/**
* Raturns all available SD-Cards in the system (include emulated)
*
* Warning: Hack! Based on Android source code of version 4.3 (API 18)
* Because there is no standart way to get it.
* TODO: Test on future Android versions 4.4+
*
* #return paths to all available SD-Cards in the system (include emulated)
*/
public static String[] getStorageDirectories()
{
// Final set of paths
final Set<String> rv = new HashSet<String>();
// Primary physical SD-CARD (not emulated)
final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
// All Secondary SD-CARDs (all exclude primary) separated by ":"
final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
// Primary emulated SD-CARD
final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
if(TextUtils.isEmpty(rawEmulatedStorageTarget))
{
// Device has physical external storage; use plain paths.
if(TextUtils.isEmpty(rawExternalStorage))
{
// EXTERNAL_STORAGE undefined; falling back to default.
rv.add("/storage/sdcard0");
}
else
{
rv.add(rawExternalStorage);
}
}
else
{
// Device has emulated storage; external storage paths should have
// userId burned into them.
final String rawUserId;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
{
rawUserId = "";
}
else
{
final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
final String[] folders = DIR_SEPORATOR.split(path);
final String lastFolder = folders[folders.length - 1];
boolean isDigit = false;
try
{
Integer.valueOf(lastFolder);
isDigit = true;
}
catch(NumberFormatException ignored)
{
}
rawUserId = isDigit ? lastFolder : "";
}
// /storage/emulated/0[1,2,...]
if(TextUtils.isEmpty(rawUserId))
{
rv.add(rawEmulatedStorageTarget);
}
else
{
rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
}
}
// Add all secondary storages
if(!TextUtils.isEmpty(rawSecondaryStoragesStr))
{
// All Secondary SD-CARDs splited into array
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
Collections.addAll(rv, rawSecondaryStorages);
}
return rv.toArray(new String[rv.size()]);
}