I'm faced with the well-known problem of obtaining the path of an external SD card mounted on some Android devices. (see this question for understanding what I mean)
I've thought to solve the problem by reading the content of /etc/vold.fstab, then taking just lines representing partitions, but I don't have a device for doing tests.
What I want to do is to read that file, ignore the row which refers to the address returned by Environment.getExternalStorageDirectory(), and take the other row (if present).
What I don't know (and I don't have the possibility to test it) is: are there cases in which I can have other lines which are not the external SD card? The SD card, if present, appears on the file vold.fstab?
edit:
The answer is: YES. Read the accepted answer.
What is wrong with this?
Environment.getExternalStoreDirectory()
Why are you ignoring this when it's the SD Card?
OK - In the case of devices with /sdcard (Internal) and an external SD card (??) you could always scan the fstab file and look for "sdhci" which is the SD Host Controller bridge driver.
Something like:
dev_mount sdcard /mnt/external_sdcard auto /devices/platform/sdhci.2/mmc_host/mmc2
Then just parse as necessary.
Why the "necessity" to find the actual SD card though when it's not actually treated as such by the OS? (Won't be mounted as mass storage)
Is your application only available for devices where this is the case? What is wrong with using whatever Android believes is the SD storage space?
I use the following code to first detect wether the sdCard exists and then run the relevent code:
Detecting whether SD card exists:
Boolean isSDPresent = android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
if(isSDPresent)
{
// file path = "/mnt/sdcard/Android/data/PACKAGE_NAME/..."
}
else
{
// file path = "/data/data/PACKAGE_NAME/..."
}
Think this is what you are after?
This could be the right solution. Read it from /etc/vold.fstab, which lists all the partitions currently mounted on a Linux system (Android included)
String getExternalSdcardDirectory() {
FileInputStream fis = null;
try {
fis = new FileInputStream(new File("/etc/vold.fstab"));
} catch (FileNotFoundException e) {
return null; // should never be reached
}
try {
byte[] buffer = new byte[4096];
int n=0;
String file = "";
while ((n=fis.read(buffer, 0, 4096))>0) {
file += new String(buffer, 0, n);
}
fis.close();
String[] rows = file.split("\n");
for (String row: rows) {
String trimmedRow = row.trim();
if (trimmedRow.startsWith("#") || trimmedRow.equals(""))
continue;
else if (trimmedRow.equals(Environment.getExternalStorageDirectory().getAbsolutePath()))
continue;
else
return trimmedRow.split(" ")[2];
}
} catch (IOException e) {
// nothing
}
return null;
}
Related
I have done done lots of googling and searching here on StackOverflow for an answer to my question. I have seen the many posts about a recursive file deletion method (like this, this, and this), but none of these have worked for me. I am using this method to find my SD card (which is /storage/0000-0000).
private void wipeSDCards() {
File storage = new File("/storage");
File[] storageSubDirs = storage.listFiles();
for (File storageSubDir : storageSubDirs) {
try {
boolean storageSubDirIsEmulated = Environment.isExternalStorageEmulated(storageSubDir);
boolean storageSubDirIsRemovable = Environment.isExternalStorageRemovable(storageSubDir);
if (!storageSubDirIsEmulated && storageSubDirIsRemovable) {
deleteStuff(storageSubDir);
}
} catch (IllegalArgumentException iae) {
LoggingUtil.i(MyClass.class, String.format(Locale.US, "FAILED ON: %s", storageSubDir.getAbsolutePath()));
}
}
}
private void deleteStuff(File fileOrDirectory) {
// things I have tried ....
}
To delete the files on the SD card I have tried:
File fileToDelete = new File('path.to.file');
fileToDelete.delete();
But that doesn't work. I have tried:
Files.delete(fileToDelete.toPath());
But that doesn't work. I have tried:
application.getContentResolver().delete(uri, null, null);
But that doesn't work either. I have placed this print statement just before all my deletion attempts:
Log.e("LOOK HERE: ", String.format(Locale.US, "Your Application %s delete this file (%s)", (fileOrDirectory.canWrite() ? "can" : "can NOT"), fileOrDirectory.getName()));
And have always gotten back Your Application can NOT delete this file (path.to.file). It seems that I do not have the authorization to delete files on the SD card, even though I can through the Windows file browser.
So, is an application allowed to delete the contents of the SD card? If so, how does one go about doing this? How about a method to just format the SD card, I would take that as well.
Also, please don't ask "why do you want to do this" ... I just do.
I have a strange problem writing on my external sd card running Android Oreo :
First the usual commands
getExternalFilesDir(null).getAbsolutePath()
or
Environment.getExternalStorageDirectory().getAbsolutePath()
return the path to the emulated storage not the real sd card sometimes called secondary external storage.
But well that's not the issue as I manage to get the path to the SD card using the method getExternalStoragePath() described at the end of my post.
Now the strange thing is that I cannot get my app (let's say the package is com.example.myapp) creating the directory
SDCARDPATH/Android/data/com.example.myapp/files
despite all necessary authorizations have been properly granted (but it is possible that the WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE apply only to the emulated storage, I have a feeling that the issue is there!) Here is what I'm doing :
String SDCARDPATH=getExternalStoragePath(this, true);
File md = new File(new File(new File(new File(SDCARDPATH,"Android"),"data"),PACKAGE_NAME),"files");
if(!md.exists()) {
try {
md.mkdirs();
} catch (Exception e) {
e.printStackTrace();
}
}
The strange thing is that if I create the directory manually through the phone file explorer the app can then write and read within that directory without any trouble.
So my question is how to properly create that directory on my external sdcard
Side note I see some app installed on my phone have managed to create their app directory on the sd card.
Finally here is the getExternalStoragePath method I use and which was grabbed on the net :
Thanks to all in advance for your help,
Lea
public 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;
}
I manage to get the path to the SD card using the method getExternalStoragePath() described at the end of my post
That code is not going to work on all versions of Android. In particular, it will not work on Android 10+. I would not be the least bit surprised if it fails on Android 9.0 as well, given that it relies upon reflection into system classes. It will also fail on older devices where those classes and methods do not exist.
First the usual commands getExternalFilesDir(null).getAbsolutePath() or Environment.getExternalStorageDirectory().getAbsolutePath() return the path to the emulated storage not the real sd card sometimes called secondary external storage
Use getExternalFilesDirs() (note the s at the end). That returns a File[]. If that array has 2+ elements, all but the first element point to directories on removable storage that you can use from your app, without any permissions. The directory should already be created, but you can call mkdirs() on the File to ensure that it exists.
I am currently working on an app, that goes through your phone and lists all available MP3 files. I managed to get this done and search for everything on the internal storage, but didnt manage to find a way using the envoirment to get to the sd card, when one is installed. This is my code - u will see a missing part when SD card is TRUE. Can you complete it?
public List<string> ReturnPlayableMp3(bool sdCard)
{
List<string> res = new List<string>();
string phyle;
if(sdCard)
{
// missing
}
else
{
try
{
var path1 = Android.OS.Environment.ExternalStorageDirectory.ToString();
var mp3Files = Directory.EnumerateFiles(path1, "*.mp3", SearchOption.AllDirectories);
foreach (string currentFile in mp3Files)
{
phyle = currentFile;
res.Add(phyle);
}
}
catch (Exception e9)
{
Toast.MakeText(ApplicationContext, "ut oh\n" + e9.Message, ToastLength.Long).Show();
}
}
return res;
}
}
It would need to return the exact same thing as it does for the internal storage only this time for the sd card. Right now, what is beeing returned is:
""/storage/emulated/0""
I hope you can help me. Thank you!
SO I found the place it is: /storage/05B6-2226/
But the digits refer to only MY sd card. How do I get this path programatically?
Take a look at these methods:
Context.GetExternalFilesDir
Returns the absolute path to the directory on the primary external
filesystem (that is somewhere on Environment.ExternalStorageDirectory)
where the application can place persistent files it owns. These files
are internal to the applications, and not typically visible to the
user as media.
Context.GetExternalFilesDirs
Returns absolute paths to application-specific directories on all
external storage devices where the application can place persistent
files it owns. These files are internal to the application, and not
typically visible to the user as media.
I've been searching for a couple of days with a lot of solutions that just ended up giving you the 'external' built in storage. Finally found this solution for the 'removable' SD Card and wanted to post it here in case someone else is still looking.
How to write on external storage sd card in mashmallow in xamarin.android
//Get the list of External Storage Volumes (E.g. SD Card)
Context context = Android.App.Application.Context;
var storageManager = (Android.OS.Storage.StorageManager)context.GetSystemService(Context.StorageService);
var volumeList = (Java.Lang.Object[])storageManager.Class.GetDeclaredMethod("getVolumeList").Invoke(storageManager);
List<Java.IO.File> ExtFolders = new List<Java.IO.File>();
//Select the Directories that are not Emulated
foreach (var storage in volumeList)
{
Java.IO.File info = (Java.IO.File)storage.Class.GetDeclaredMethod("getDirectory").Invoke(storage);
if ((bool)storage.Class.GetDeclaredMethod("isEmulated").Invoke(storage) == false && info.TotalSpace > 0)
{
//Get Directory Path
Console.WriteLine(info.Path);
}
}
Just wanna share my answer, where I have get the extStorages Path and I use this method in my simple file browser app.
public static string[] GetRemovableStorages()
{
List<string> extStorage = new List<string>();
//If this throws exception
string storageDir = (string)Environment.StorageDirectory;
//Try this
string storageDir = Directory.GetParent (Environment.ExternalStoragePublicDirectory).Parent.FullName;
string[] directories = Directory.GetDirectories(storageDir);
foreach(string dir in directories)
{
try
{
var extStoragePath = new Java.IO.File(dir);
bool isRemovable = Environment.InvokeIsExternalStorageRemovable(extStoragePath);
if(isRemovable) extStorage.Add(extStoragePath.AbsolutePath);
else return null;
}
catch
{
}
}
return extStorage.ToArray();
}
Elikill58's answer throws exception no such method "getDirectory" in my case but I recommend Elikill58's answer
I have a device with an SD card. Now I want to check that device has mounted an external SD card and can read files from the public DCIM folder. I know that I can use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);, but this method returns only files, that is on primary external memory, not on the mounted SD card (Secondary external storage).
I found that Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); returns an element with index 1, for the secondary external storage, but this method gets files only for application sandbox (Android/data/packagename). So my question is how get path to secondary external path for public directory like DCIM?
I found solution, here is code snippet:
String strSDCardPath = System.getenv("SECONDARY_STORAGE");
if ((strSDCardPath == null) || (strSDCardPath.length() == 0)) {
strSDCardPath = System.getenv("EXTERNAL_SDCARD_STORAGE");
}
//If may get a full path that is not the right one, even if we don't have the SD Card there.
//We just need the "/mnt/extSdCard/" i.e and check if it's writable
if(strSDCardPath != null) {
if (strSDCardPath.contains(":")) {
strSDCardPath = strSDCardPath.substring(0,strSDCardPath.indexOf(":"));
}
File externalFilePath = new File(strSDCardPath);
if (externalFilePath.exists() && externalFilePath.canWrite()) {
//do what you need here
}
}
For more details, read here: Finding the SDCard Path on Android devices
Have you tried this?
private boolean canWriteToFlash() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// Read only isn't good enough
return false;
} else {
return false;
} }
For accessing multiple external storage, you could use the api
ContextCompat.getExternalCacheDirs(Context context);
ContextCompat.getExternalFilesDirs(Context context, String type);
This will return a path like /storage/sdcard1/Android/data/com.example.foo/, then you can replace Android/data/com.example.foo/ with DCIM to get the path you want.
This method is not reliable, because the path Android/data/com.example.foo/ will be different or changed in the future, but it worths to have a try.
You can see more information from the official android documents.
We've just fallen foul of the new permissions that apply to writing files to sd cards (external storage) on Android 4.4 (EACCES Permission Denied)
Prior to KitKat we set our writable folder like this:
mfolder = Environment.getExternalStorageDirectory().getPath() + "/appfiles";
However after hours of searching I've come to the conclusion, rightly or wrongly that on 4.4 devices to enable writing of files this needs to be changed to:
mfolder = Environment.getExternalStorageDirectory().getPath() + "/Android/data/com.xyz.abc/appfiles";
So mfolder would be something like: /mnt/sdcard/Android/data/com.xyz.abc/appfiles
Is this correct, do we create a folder like the one above on the sdcard to enable 4.4 devices to write files?
mfolder is a String that we save to shared preferences.
Then we have this code that runs once if API>=19 that changes the mfolder String and then copies all the files from the old folder to the new 'kitkat' folder.
if (android.os.Build.VERSION.SDK_INT>=19){
if (!mfolder.contains("/Android/data/com.xyz.abc/appfiles")){
if (prefs.getBoolean("kitkatcheck", false)==false){
//update mfolder from
// /mnt/sdcard/appfiles
// to
// /mnt/sdcard/Android/data/com.xyz.abc/appfiles
String prekitkatfolder = mfolder;
String kitkatfolder = mfolder.replace("/appfiles", "/Android/data/com.xyz.abc/appfiles");
mfolder = kitkatfolder;
try {
File sd = new File(mfolder);
if(!sd.exists() || !sd.isDirectory()) {
sd.mkdirs();
}
} catch (Exception e) {
Toast.makeText(getBaseContext(), "Error creating Kitkat folder!\n" + e.toString(), Toast.LENGTH_LONG).show();
return;
}
prefEditor.putString("patternfolder", mfolder);
prefEditor.putBoolean("kitkatcheck", true);
prefEditor.commit();
//copy files and folder from old appfiles folder to new.
AllFiles.clear();
listFilesAndFilesSubDirectories(prekitkatfolder);
if (AllFiles.size()>0){
for (File child : AllFiles ) {
try {
File dest = new File(child.toString().replace(prekitkatfolder, kitkatfolder));
try {
String filePath = dest.getPath().substring(0, dest.getPath().lastIndexOf(File.separator));
File subfolder = new File(filePath);
if(!subfolder.exists() || !subfolder.isDirectory()) {
subfolder.mkdirs();
}
} catch (Exception ex) {
}
copyFile(child, dest);
} catch (Throwable t) {
}
}
}
}
}
I then notify the user that their files have been copied to the new folder and that due to the new permissions they would have to manually delete the old prekitkatfolder folder. I guess they will only be able to do this if they have a stock file manager or if they unmounted sd card and place it in a PC, due to the new 4.4 permissions?
Also, for us it appears that these 4.4 permissions are not affecting all our users with Kitkat. Some can still write to the original folder location on their external storage and some get the EACCES (Permission Denied) error. Can anyone throw any light on why this might be, one would think it would apply to all 4.4 devices using external storage?
As we have no actual 4.4 device we are having to test this code using the emulator (API 19) but we do not get the EACCES Permission Denied error. So we released a beta version with code above and have been told that the copied files ended up in internal storage, how can that be?
Any ideas what we're doing wrong, thanks in advance
Updated solution.
This sets and also creates the folder in the correct place for KitKat.
mfolder = this.getExternalFilesDir("asubfoldername").getAbsolutePath();
However, this isn't full-proof, if the Android device has both an internal and external secondary storage locations, the above will use the internal one. Not really what we want as we require path to removable sdcard or better still the path to the secondary storagelocation with the most free available space.
File[] possible_kitkat_mounts = getExternalFilesDirs(null);
Note the "s" on the end of getExternalFilesDirs. This creates an array of secondary external storage locations.
for (int x = 0; x < possible_kitkat_mounts.length; x++) {
//Log.d("test", "possible_kitkat_mounts " + possible_kitkat_mounts[x].toString());
boolean isgood=false;
if (possible_kitkat_mounts[x] != null){
isgood = test_mount(possible_kitkat_mounts[x].toString());
if (isgood==true){
arrMyMounts.add(newymounts(Device_get_device_info(possible_kitkat_mounts[x].toString()), possible_kitkat_mounts[x].toString()));
}
}
}
//sort arrMyMounts size so we can use largest
Collections.sort(arrMyMounts, new Comparator<mymounts>(){
public int compare(mymounts obj1, mymounts obj2){
return (obj1.avaliablesize > obj2.avaliablesize) ? -1: (obj1.avaliablesize > obj2.avaliablesize) ? 1:0 ;
}
});
if (arrMyMounts.size()>0){
mfolder = arrMyMounts.get(0).name + "/asubfoldername";
//Log.d("test", "selected kitkat mount " + kitkatfolder);
}else{
//do something else...
}
From the array of possible_kitkat_mounts we check via test_mount to see if we can actually write to the selected location and if successful we add that location to arrMyMounts.
By sorting arrMyMounts we can then get the location with the most available free space.
Hey presto, arrMyMounts.get(0).name is a kitkat secondary storage location with the most free space.
Google has blocked write access to external storage devices in Android 4.4. Until they change it there is no way to revert it back without root.
More info: https://groups.google.com/forum/#!msg/android-platform/14VUiIgwUjY/UsxMYwu02z0J
It might be working on some devices with Kitkat which have minisd card slot. It is confusing :(