How to get writable DocumentFile Uri from a Media real path? - android

I have a situation in Lollipop where I have:
Directory tree (DocumentFile) which is granted from user.
A collection of audio files that need to modify retrieved from media provider.
Uri mediaUri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
"45");
Then, how to write a stream into one of those files?
I think, this https://stackoverflow.com/a/30514269/615025 gives a work around. Copy bytes of the file to cache directory, do modification on the temporary file and then copy the bytes back to the original file.
DocumentFile is a new mechanism which allows us to modify files in Lollipop. It is not the same case with this post how to get contact photo URI
Thanks

You can open an outputStream directly from a Uri using ContentResolver.openOutputStream(Uri)
For example,
OutputStream fos;
fos = new BufferedOutputStream(context.getContentResolver().openOutputStream(uri));
Optionally, openOutputStream takes a string to specify mode.
i.e
openOutputStream(uri, "w");
to write or
openOutputStream(uri, "wa");
to append.

Related

File created via Storage Access Framework shows up as 0 bytes in size, but it's not empty and can be opened properly

I've created a JPEG file using SAF like so:
public int createPublicFile(String fileName, String subfolder, String mimeType) {
Uri parentUri = folderForImagesUri(); // Obtained earlier via Intent.ACTION_OPEN_DOCUMENT_TREE
DocumentFile folder = DocumentFile.fromTreeUri(this, parentUri);
DocumentFile subfolderUri = TextUtils.isEmpty(subfolder) ? folder : folder.createDirectory(subfolder);
DocumentFile file = subfolderUri.createFile(mimeType, fileName);
ParcelFileDescriptor descriptor = getContentResolver().openFileDescriptor(file.getUri(), "rw");
return descriptor.detachFd();
}
The file descriptor was then passed to a native library that has written JPEG data into this fd with write() and closed it with close(). The MIME type I specified is "image/jpeg".
Here's the problem: the built-in Android 11 "Files" app shows it as 0 bytes in size, and yet the file seems well-formed since I can open it by tapping, the thumbnail is also generated correctly. Why is it showing as 0 B, did I do something wrong?

Creating a writeable DocumentFile from URI

I'm trying to adapt a File-based document system to something using DocumentFile in order to allow external storage read/write access on API >= 29.
I get the user to select the SD card root using Intent.ACTION_OPEN_DOCUMENT_TREE, and I get back a Uri as expected, which I can then handle using:
getContentResolver().takePersistableUriPermission(resultData.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
I can browse successfully through the external storage contents up to the selected root. All good.
But what I need to be able to do is write an arbitrary file in the chosen (sub)folder, and that's where I'm running into problems.
DocumentFile file = DocumentFile.fromSingleUri(mContext, Uri.parse(toPath));
Uri uri = file.getUri();
FileOutputStream output = mContext.getContentResolver().openOutputStream(uri);
Except on the openOutputStream() call I get:
java.io.FileNotFoundException: Failed to open for writing: java.io.FileNotFoundException: open failed: EISDIR (Is a directory)
That's slightly confusing to me, but the "file not found" part suggests I might need to create the blank output file first, so I try that, like:
DocumentFile file = DocumentFile.fromSingleUri(mContext, Uri.parse(toPath));
Uri uri = file.getUri();
if (file == null) {
return false;
}
if (file.exists()) {
file.delete();
}
DocumentFile.fromTreeUri(mContext, Uri.parse(getParentPath(toPath))).createFile("", uri.getLastPathSegment());
FileOutputStream output = mContext.getContentResolver().openOutputStream(uri);
I get a java.io.IOException:
java.lang.IllegalStateException: Failed to touch /mnt/media_rw/0B07-1910/Testing.tmp: java.io.IOException: Read-only file system
at android.os.Parcel.createException(Parcel.java:2079)
at android.os.Parcel.readException(Parcel.java:2039)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:188)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:658)
at android.content.ContentResolver.call(ContentResolver.java:2042)
at android.provider.DocumentsContract.createDocument(DocumentsContract.java:1327)
at androidx.documentfile.provider.TreeDocumentFile.createFile(TreeDocumentFile.java:53)
at androidx.documentfile.provider.TreeDocumentFile.createFile(TreeDocumentFile.java:45)
Which doesn't make sense to me, since the tree should be writeable.
For what it's worth, the Uri I get back from Intent.ACTION_OPEN_DOCUMENT_TREE looks like this:
content://com.android.externalstorage.documents/tree/0B07-1910%3A
Interestingly, when I use that Uri to create a DocumentFile object to browse, using documentFile = DocumentFile.fromTreeUri(context, uri), then documentFile.getURI().toString() looks like:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3A
i.e., it's had something appended to the end of it.
Then, I descend into what should be a writeable folder (like "Download"), and try creating a writeable file as described above. The "Download" folder gets the Uri:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3ADownload
and the Uri I'm using for toPath, above, is then:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3ADownload/Testing.tmp
which leads to the problems described previously trying to create it.
I haven't actually found any decent information about writing an arbitrary file under Storage Access Framework restrictions.
What am I doing wrong? Thanks. :)
Uri uri = uri obtained from ACTION_OPEN_DOCUMENT_TREE
String folderName = "questions.59189631";
DocumentFile documentDir = DocumentFile.fromTreeUri(context, uri);
DocumentFile folder = documentDir.createDirectory(folderName);
return folder.getUri();
Use createFile() for a writable file.

File operations in android Q beta

1)Target set to Android Q with android.permission.WRITE_EXTERNAL_STORAGE
2) use getExternalStorageDirectoryor getExternalStoragePublicDirectoryand FileOutputStream(file)saving file throws
java.io.FileNotFoundException: /storage/emulated/0/myfolder/mytext.txt open failed: ENOENT (No such file or directory)
3) use getExternalFilesDirapi and saving is success but wont show up even after MediaScannerConnection.scanFile.
/storage/emulated/0/Android/data/my.com.ui/files/Download/myfolder/mytext.txt
What is best way to copy file from internal memory to SDCARD in android Q and refresh.
In Android API 29 and above you can use the below code to store files, images and videos to external storage.
//First, if your picking your file using "android.media.action.IMAGE_CAPTURE"
then store that file in applications private Path(getExternalFilesDir()) as below.
File destination = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
//Then use content provider to get access to Media-store as below.
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
//if you want specific mime type, specify your mime type here. otherwise leave it blank, it will take default file mime type
values.put(MediaStore.Images.Media.MIME_TYPE, "MimeType");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "path"); // specify
storage path
// insert to media-store using content provider, it will return URI.
Uri uri = cxt.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
//Use that URI to open the file.
ParcelFileDescriptor descriptor = context.getContentResolver().openFileDescriptor(uri,"w"); //"w" specify's write mode
FileDescriptor fileDescriptor = descriptor.getFileDescriptor();
// read file from private path.
InputStream dataInputStream = cxt.openFileInput(privatePath_file_path);
//write file into out file-stream.
OutputStream output = new FileOutputStream(fileDescriptor);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = dataInputStream.read(buf)) > 0)
{
output.write(buf, 0, bytesRead);
}
datanputStream.close();
output.close();
}
In Android Q direct File access is disabled by default for the apps outside their private folders. Here few strategies you can use:
Use the manifest option requestLegacyStorage to have the old behavior but it won't work anymore with Android R, so it's really a short term solution;
Save your files using getExternalFilesDir() method. It's your private folder, other apps could access these files only if they have READ_EXTERNAL_STORAGE permission. In this case it would be good to use FileProvider to grant access to other apps to your files.
Use MediaStore API to insert your media directly. This approach is good for videos, music and photos.
Use the method getPrimaryStorageVolume().createOpenDocumentTreeIntent() of class StorageManager to ask for access to the extenal primary volume. In this case you need user consent and you won't be able to use File api directly anyway, but using DocumentFile class you have a very similar interface, so this is the solution closer to the old behavior. It works if you need to perform operations in foreground and background, i.e. without user interaction with the exception the first interaction to request the permission.
Use ACTION_CREATE_DOCUMENT to create a file using SAF. This option is valid if you do this operation in foreground because you will need the folder selection by the user.
I link Flipper library for point 4, it helps to manage files like in older android versions.
Temporary possibility: you can use android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest.
Add android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest, which will allow you to use All File APIs, for now.
1)Use Document-provider https://developer.android.com/guide/topics/providers/document-provider#create
2)User will be asked to save(total number of files) and get stored in documents folder.
https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386
If you use preserveLegacyExternalStorage, the legacy storage model remains in effect only until the user uninstalls your app. If the user installs or reinstalls your app on a device that runs Android 11, then your app cannot opt out the scoped storage model, regardless of the value of preserveLegacyExternalStorage.
Something to remember when using this flag. know more

How to pick file from external storage using file picker in Android

I am having a problem with selecting image file from external storage using file picker in Android. This question is the consequence of this question - No such file or diectory error in image file upload using Retrofit in Android. What my problem is opening and reading file from external storage on activity result. I want to convert result URI into File.
I read a pdf file from download folder on activity result
Uri bookUri = data.getData();
if(bookUri!=null)
{
String filePath = bookUri.toString();//bookUri.toString()
String mime = app.getMimeType(filePath);
if(mime!=null && !mime.isEmpty() && (mime.toLowerCase()=="application/pdf" || mime.toLowerCase()=="application/txt" || mime.toLowerCase()=="application/text"))
{
bookFile = new File(bookUri.getPath());
ivBookFile.setImageResource(R.drawable.book_selected);
}
else{
Toast.makeText(getBaseContext(),"Unable to process file you have chosen.",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(bookUri.getPath()); to convert into File. The above code works well. It is working. The problem is now I am trying to open an image file in DCIM/Camera folder on activity result.
This is the code I used
Uri selectedImageUri = data.getData();
if(selectedImageUri!=null)
{
try{
bmpCoverImage = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImageUri);
imageFile = new File(selectedImageUri.getPath());
if(bmpCoverImage!=null)
{
ivCoverImage.setImageBitmap(bmpCoverImage);
}
}
catch (IOException e)
{
Toast.makeText(getBaseContext(),"An error occurred with the file selected",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(selectedImageUri.getPath()); like I did in reading pdf file. This time the code is not working. When I do operation with the file like in previous question, it gives me error.
I used this way also
imageFile = new File(Environment.getExternalStorageDirectory(),selectedImageUri.getPath());
I got the same error. How can I open the image file correctly from external storage? How can I convert the chosen file URI from external storage into File?
I am having a problem with selecting image file from external storage using file picker in Android
If you are referring to the code that you are using in this question, you are not "using file picker". You are using ACTION_GET_CONTENT, which has never been a "file picker", nor will it ever be a "file picker".
I want to convert result URI into File.
Usually, that is not necessary. But, if that is what you want to do:
use ContentResolver and openInputStream() to get an InputStream on the content represented by the Uri
create a FileOutputStream on your desired file
use Java I/O to copy the bytes from the InputStream into the FileOutputStream
The above code works well. It is working.
It works for the small number of devices that you tested, for the specific activities that the user chose to handle the ACTION_GET_CONTENT request. It will not work on most Android devices, and it will not work in most circumstances. The only time that code will work is if the Uri has a file scheme. Most of the time, it will not. Instead, it will have a content scheme, representing content supplied by a ContentProvider.
Please how can I open the image file correctly from external storage?
If you wish to continue using ACTION_GET_CONTENT, please understand that this has nothing to do with external storage specifically. You are not getting a file, on external storage or elsewhere. You are getting a Uri. This is akin to a URL, such as the URL for this Web page. Just as a URL does not necessarily point to a file on your hard drive, a Uri does not necessarily point to a file on the filesystem. Use a ContentResolver and DocumentFile to work with the Uri and the content that it identifies.
If you want to always get files on external storage (and nowhere else), then use an actual file picker library.

How to create new file in secondary SD card of Android Lollipop device?

In my app I use MediaMuxer to create a video file. Constructor of MediaMuxer requires absolute path of output file. Everything is ok with primary storage, but for secondary storage app doesn't work. As I check, in Lollipop user has to pick output directory in external storage, using new Intent ACTION_OPEN_DOCUMENT_TREE (How to use the new SD card access API presented for Android 5.0 (Lollipop)?)
But on result of this Intent we get DocumentFile. I don't know how to create new video file with MediaMuxer and this DocumentFile. Can someone help me?
There is a method createFile(String mimeType, String displayName) - just call it on the DocumentFile directory instance:
DocumentFile newFile = documentFileDir.createFile("application/octet-stream", fileName);
OutputStream os = getContentResolver().openOutputStream(newFile.getUri());
... // write your data

Categories

Resources