I'm using SAF so that users can store videos on their external SD cards or even USB drives with my camera app. I don't want them to appear in the gallery though, so I need to create a .nomedia file.
I'm using this code currently:
mTreeUri = Uri.parse(treeUri);
mPickedDir = DocumentFile.fromTreeUri(this, mTreeUri);
if (treeUri != null) {
final DocumentFile existingMediaFile = mPickedDir.findFile(".nomedia");
if (existingMediaFile == null) {
DocumentFile newFile = mPickedDir.createFile("text/plain", ".nomedia");
if (newFile == null) {
return null;
}
OutputStream out = null;
try {
out = getContentResolver().openOutputStream(newFile.getUri());
out.write("A long time ago...".getBytes());
out.close();
} catch (IOException e) {
Log.d(TAG, "Nomedia", e);
}
The problem is that SAF creates a file called nomedia.txt instead of the dotfile. I played around with a few different mime types in the application/* range but then no file gets created at all. Glad for any pointers :)
And found it. Using any kind of custom mime type like "thisis/awesome" seems to work just fine.
A little bit late for an answer, but I would suggest something like:
new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_MUSIC), ".nomedia").createNewFile();
"application/octet-stream" works fine. That's the default mime type for cases when something more specific doesn't apply.
Related
I really need your help because I'm stuck <.<
I have already tryed all solutions I've found here in "stackoverflow" for this problem, but any of them worked for me..
I have updated my application to follow the new Google Play Store policies, so actually my app is using only "Scoped Storage" without "Shared Storage" behaviour, so I've removed from the manifest the "requestLegacyExternalStorage".
In my app I need to send to the server some file selected by the user, these file can be stored in the "internal storage" or in the "external storage". They are always stored outside of the app-specific directory, so the directory in:
"/storage/emulated/0/Android/data/[APP_PACKAGE]/files"
My biggest problem is that I can't open any file stored outside of the app-specific directory!
I really need to open the files that the user has selected and convert its content to "base64" to send it to the server.
But when I use this method to get the file content encoded in base64:
public static String fileToBase64(#NonNull String filePath) {
String ret = null;
byte[] buffer = new byte[8192];
int bytesRead;
try(ByteArrayOutputStream bAOS = new ByteArrayOutputStream();
Base64OutputStream b64OS = new Base64OutputStream(bAOS, Base64.DEFAULT);
InputStream iS = new FileInputStream(filePath)){
while ((bytesRead = iS.read(buffer)) != -0x1) {
b64OS.write(buffer, 0x0, bytesRead);
}
b64OS.flush();
ret = bAOS.toString();
} catch (IOException ioE) {
Logger.onException(TAG, ioE);
}
return ret;
}
I always get an ACCESS PERMISSION DENIED.
Is there any way to solve this problem without using "requestLegacyStorage" in the manifest?
I know that Google will remove all applications that don't use only "SCOPED STORAGE" from the store starting from 5 of july, so I can't use the "reqestLegacyStorage" to solve this problem...
I'm already requesting and giving the "READ_EXTERNAL_STORAGE" and "WRITE_EXTERNAL_STORAGE" permissions, but I still can't open the file content if it is stored outside of the dedicated application directory...
I can only open and encode file content if it is in the app directory (so: "/storage/emulated/0/Android/data/[APP_PACKAGE]/files"), but I need to upload to my server files choosed by the user so these files were never stored inside the app directory, they are always stored inside the internal storage or the external storage in directories like the "Download" dir or the "Pictures" dir (I need to upload every type of files, so pictures, documents, pdfs ecc.. ecc.)
Is there any solution to this problem?
I have already tryed all the solutions I found online, but I always get an ACCESS EXCEPTION if I don't add "requestLegacyStorage" to the manifest (and I can't add it to notg go against Google's policies...)
Please I really need to solve this problem because this is one of the most important feature of my app..
Thank you so much!
I hope anybody can help me solve this problem T_T
Have a nice day and nice coding!
(Ask if you need more informations and I will add them!)
##########################################################################
If anyone needs it I found a "solution" but works only by using "ACTION_GET_CONTENT" (and probably by using "ACTION_OPEN_DOCUMENT", but I didn't try it yet).
When you select a file (stored outside the app-specific directory) using "ACTION_GET_CONTENT" this file is copied inside the app-specific directory ("/storage/emulated/0/Android/data/[APP_PACKAGE]/files") so you can open it because it agrees with the "SCOPED STORAGE" policy.
# "ACTION_GET_CONTENT" code:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
String[] mimes = {
"application/*",
"audio/*",
"font/*",
"image/*",
"message/*",
"model/*",
"multipart/*",
"text/*",
"video/*"
};
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(Intent.createChooser(intent, getString(R.string.msg_select_file_to_upload)), REQ_CODE_OPEN_DOCUMENT);
Theoretically it also works without passing the "mimes" array to the intent extra "EXTRA_MIME_TYPES".
To get the path inside the "onActivityResult":
String path = FilesUtils.onActivityResultOpenDocument(this, data);
public static String onActivityResultOpenDocument(Context context, Intent data){
String selectedPath, fileName;
Uri uri = data.getData();
String mimeType = context.getContentResolver().getType(uri);
if (mimeType == null) {
String path = getPathFromOpenDocumentUri(context, uri);
if (path == null) {
fileName = FilenameUtils.getName(uri.toString());
} else {
File file = new File(path);
fileName = file.getName();
}
} else {
Uri returnUri = data.getData();
Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
fileName = returnCursor.getString(nameIndex);
}
String sourcePath = context.getExternalFilesDir(null).toString();
selectedPath = formatFilepath(sourcePath, fileName);
File fileSave = new File(selectedPath);
try {
copyUriStreamToFile(context, uri, fileSave);
} catch (Exception e) {
Logger.onException(TAG, e);
Toast.makeText(context, R.string.error_impossibile_recuperare_file, Toast.LENGTH_SHORT).show();
selectedPath = null;
}
return selectedPath;
}
So summarizing by using "ACTION_GET_CONTENT" (and maybe "ACTION_OPEN_DOCUMENT" too, but I didn't tryed this) the selected file is copied inside the app-specific directory (so: "/storage/emulated/0/Android/data/[APP_PACKAGE]/files") in this way the file can be opened because it agrees with the "Scoped Storage" policy.
Thank you all for your answers and your time! (: <3
I still don't know how to read a file if it is stored outside the app-specific directory, so if anybody know it please share your solution (:
I'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent. Here's a sample:
// getRealPathFromURI(intent.getData());
private String getRealPathFromURI(Uri contentURI) {
String result;
Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) { // Source is Dropbox or other similar local file path
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
Just like this answer.
Recently updated the compileSdkVersion to 29 and apparently the DATA attribute everyone's using is deprecated.
In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.
Only thing i found is this question. Didn't find a proper answer there though.
Please help me overcome that deprecation issue with a solution using the suggested way or any other way.
Thank you.
Update:
Followed #CommonsWare's answer and copied the returned Uri (of an image the user picked) to a local directory, using context.getContentResolver.openInputStream(Uri).
Even tried retrieving a file from Google Drive - and it worked. Only problem was the long time it took (about 20 sec for 5MB file).
As a bonus, i was cleared to remove external storage permissions, which one doesn't need for using app's local directories.
No more externals paths for me!
This question came up for me too a week ago.
My solution was to create an InputStream from the URI and then, from this, create an OutputStream by copying the contents of the input stream.
Note: You could call this method using an asynchronous call because copying extremely large files could have some delays and you won't want to block your UI
#Nullable
public static String createCopyAndReturnRealPath(
#NonNull Context context, #NonNull Uri uri) {
final ContentResolver contentResolver = context.getContentResolver();
if (contentResolver == null)
return null;
// Create file path inside app's data dir
String filePath = context.getApplicationInfo().dataDir + File.separator
+ System.currentTimeMillis();
File file = new File(filePath);
try {
InputStream inputStream = contentResolver.openInputStream(uri);
if (inputStream == null)
return null;
OutputStream outputStream = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0)
outputStream.write(buf, 0, len);
outputStream.close();
inputStream.close();
} catch (IOException ignore) {
return null;
}
return file.getAbsolutePath();
}
I'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent.
That code may not work for all images. There is no requirement for DATA to point to a filesystem path that you can access.
Just like this answer.
FWIW, this was my answer to that question.
Only thing i found is this question. Didn't find a proper answer there though.
That technique wasn't particularly good and will no longer work, as Android has locked down /proc.
In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.
The more general concept is that you use ContentResolver to work with the Uri, whether you get an InputStream (openInputStream()), OutputStream (openOutputStream()), or FileDescriptor. Consume the content using those things. If you have some API that absolutely needs a file, copy the content (e.g., from the InputStream) to a file that you control (e.g., in getCacheDir()).
As a bonus, now your code is also in position to use the Storage Access Framework (e.g., ACTION_OPEN_DOCUMENT) and the Internet (e.g., OkHttp), if and when that would be useful.
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'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent. Here's a sample:
// getRealPathFromURI(intent.getData());
private String getRealPathFromURI(Uri contentURI) {
String result;
Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) { // Source is Dropbox or other similar local file path
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
Just like this answer.
Recently updated the compileSdkVersion to 29 and apparently the DATA attribute everyone's using is deprecated.
In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.
Only thing i found is this question. Didn't find a proper answer there though.
Please help me overcome that deprecation issue with a solution using the suggested way or any other way.
Thank you.
Update:
Followed #CommonsWare's answer and copied the returned Uri (of an image the user picked) to a local directory, using context.getContentResolver.openInputStream(Uri).
Even tried retrieving a file from Google Drive - and it worked. Only problem was the long time it took (about 20 sec for 5MB file).
As a bonus, i was cleared to remove external storage permissions, which one doesn't need for using app's local directories.
No more externals paths for me!
This question came up for me too a week ago.
My solution was to create an InputStream from the URI and then, from this, create an OutputStream by copying the contents of the input stream.
Note: You could call this method using an asynchronous call because copying extremely large files could have some delays and you won't want to block your UI
#Nullable
public static String createCopyAndReturnRealPath(
#NonNull Context context, #NonNull Uri uri) {
final ContentResolver contentResolver = context.getContentResolver();
if (contentResolver == null)
return null;
// Create file path inside app's data dir
String filePath = context.getApplicationInfo().dataDir + File.separator
+ System.currentTimeMillis();
File file = new File(filePath);
try {
InputStream inputStream = contentResolver.openInputStream(uri);
if (inputStream == null)
return null;
OutputStream outputStream = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0)
outputStream.write(buf, 0, len);
outputStream.close();
inputStream.close();
} catch (IOException ignore) {
return null;
}
return file.getAbsolutePath();
}
I'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent.
That code may not work for all images. There is no requirement for DATA to point to a filesystem path that you can access.
Just like this answer.
FWIW, this was my answer to that question.
Only thing i found is this question. Didn't find a proper answer there though.
That technique wasn't particularly good and will no longer work, as Android has locked down /proc.
In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.
The more general concept is that you use ContentResolver to work with the Uri, whether you get an InputStream (openInputStream()), OutputStream (openOutputStream()), or FileDescriptor. Consume the content using those things. If you have some API that absolutely needs a file, copy the content (e.g., from the InputStream) to a file that you control (e.g., in getCacheDir()).
As a bonus, now your code is also in position to use the Storage Access Framework (e.g., ACTION_OPEN_DOCUMENT) and the Internet (e.g., OkHttp), if and when that would be useful.
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;
}