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.
Related
How to get the path of removable storage (removable SD Card) on Android 10, especially samsung phones.
As the location of the removable storage is now of the format /storage/???? ????
I found it hard to get the path as it changes with each phone.
I am posting the solution as well, it has worked well on Samsung and Mi phones.
On every phone or emulator you get the path to app specific folder on removable micro sd card by using the second item returned by
getExternalFilesDirs()
The path is different for every micro sd card used.
I would like to share a solution which took me days to find and hopefully shall help others as well. The removable sd card storage on latest android versions is like "/storage/???? ????", this location is quite hard to get. I found the following code from here:
This code helps get the correct location even on Samsung phones. I have tried it on Samsung and Mi phones.
/**
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;
}
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've been searching for many topics about android file writing, yet most of them wanted to write files to android internal storage. Others who wanted to write files on external SD card didn't success at all. My case is quite similar but I think that writing files to external USB is a totally different case.
I am using Samsung galaxy Note II running stock TouchWiz 4.4.2 [not rooted]. My phone supports micro-USB-OTG and I can mount my USB as rwxrwx--x without rooting. The complete path of my USB is /storage/UsbDriveA.
I've tried to use Environment.getExternalStorageDirectory() to get the path or use the path (mentioned above) directly but neither of them succeed. The first one returns internal storage path and the second one returns an error with "permission denied". I have already put the
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
in Android Manifest so I wondered why my code didn't work.
Moreover, I can write anything to my USB using Root Browser (use it without root) and Simple Browser thus I believe that there's a way to do that.
Here's my code:
File file = new File(path.getAbsolutePath(), "test.txt");
// File file = new File("/storage/extSdCard","test.txt");
err = false;
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.print(get);
pw.flush();
pw.close();
f.close();
}catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "writing error",Toast.LENGTH_LONG).show();
err = true;
}
Log.i("File Path:", file.getPath());
From android 4.4, you can use Storage Access Framework to access to removable media (see https://commonsware.com/blog/2014/04/09/storage-situation-removable-storage.html).
For example, I tried with success to copy a pdf file from local memory to removable memory connected by OTG adapter. The only limitation: the user has to choose a destination folder.
1) call Intent.ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, file.getName());
startActivityForResult(intent, REQUEST_CODE);
2) intercept the return intent
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == REQUEST_CODE) {
if (resultCode != RESULT_OK) return;
copyFile(fileToCopy, data.getData());
}
}
3) use the ContentResolver to open the outputStream and use it to copy the file
private void copyFile(File src, Uri destUri) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(getContentResolver().openOutputStream(destUri));
byte[] buf = new byte[1024];
bis.read(buf);
do {
bos.write(buf);
} while(bis.read(buf) != -1);
} catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) bis.close();
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
From https://source.android.com/devices/storage/
Starting in Android 4.4, ...
The WRITE_EXTERNAL_STORAGE permission must only grant write access to
the primary external storage on a device. Apps must not be allowed to
write to secondary external storage devices, except in their
package-specific directories as allowed by synthesized permissions.
Restricting writes in this way ensures the system can clean up files
when applications are uninstalled.
So, starting from Android 4.4 in devices with multiple external storages you will be able to write only on the primary external storage. Take into account that External Storage does not mean only "real external" devices. It is defined as follows (from the External Storage reference)
External storage can be provided by physical media (such as an SD
card), or by exposing a portion of internal storage through an
emulation layer.
Anyway there is a workaround to write to secondary external storage using the media content provider. Take a look at http://forum.xda-developers.com/showthread.php?t=2634840
I have used it on a project of mine, but as the author says, it's far from the ideal solution, and it is not guaranteed to work on coming Android versions, so you must not let all your app to rely on this workaround.
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 :(
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;
}