I'm working on an app that allows users to create backup files and then re-import them with a file chooser. I export the file using the following code:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/*");
startActivityForResult(intent, Constants.FILE_CHOOSER);
The code returns a URI in onActivityResult():
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == Constants.FILE_CHOOSER) {
Uri uri = data.getData();
}
I have a File that references a file within my app's local storage that contains data. I would simply like to take the file from the app's internal storage and save it to whatever directory the user selected. I can't get it to work. How do I convert the returned Uri into a File so I can write data more easily?
I would simply like to take the file from the app's internal storage and save it to whatever directory the user selected
The user didn't select a directory. The user is creating a document, where the Uri points to that document.
How do I convert the returned Uri into a File so I can write data more easily?
You don't, any more than you convert an HTTPS URL into a File so you can write data more easily.
Use ContentResolver and openOutputStream() to open an OutputStream on the (empty) document created by ACTION_CREATE_DOCUMENT and identified by the returned Uri. Then, use standard Java I/O to copy the bytes from your file (e.g., via a FileInputStream) to the OutputStream.
Related
I'm calling an intent using this:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, 42);
and selecting the root of my external SD card then taking the persisting permissions using this:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 42) {
Uri treeUri = data.getData();
this.getContentResolver().takePersistableUriPermission(treeUri, (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
}
}
I can write files using an outputstream to the root (my initial selection) but I can not write to any other sub folders unless I ask for permission to them again and explicitly select them.
I am not doing anything fancy right now to determine what folders I have permissions to, I'm just creating a POC and trying to write files to sub folders of a folder that I have selected and given permissions to. I get a null exception on the DocumentFile object for the destination variable below using this:
DocumentFile dir = DocumentFile.fromTreeUri(cont, Uri.parse(myUriToRoot+"/subfolder"));
DocumentFile destination = dir.createFile("image/jpeg", "myfile.jpg");
Again, if I explicitly give permission to that subfolder it works. Everything I've read says that all existing subfolders and any newly created folders underneath the folder to which you've given permissions should allow access to write files but it's not working for me.
What am I missing?
TIA
EDIT: I've also tried using the URL encoding characters for the slashes in my URI and still doesn't work unless I select the subfolder directly with another call to my intent.
I was able to resolve this by instead of setting the fromTreeUri() method to the subfolder directly, keeping that at the root and then using findFile on another DocumentFile object, then using createFile on that next DocumentFile object.
In my app I need to read a settings file, and that settings file can either be on local storage or on the user's Google Drive storage (with Google Drive app installed).
The following opens up a file chooser, first asking the user which file picker to use, including the option of using the Google Drive file picker:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("file/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
Intent chooserIntent = Intent.createChooser(intent, "Open file");
startActivityForResult(chooserIntent, REQUEST_CODE_FILE_PICKER);
In my onActivityResult() function, if the user opted to use a file picker to choose a local file, then I already know how to successfully get the file path and read the file. But if the user instead used the Google Drive file picker to choose a remote file, how do I access the file that the user selected?
These are the bare bones of my onActivityResult() function:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_FILE_PICKER && resultCode == RESULT_OK) {
if (LOCAL FILE) {
Uri uri = data.getData();
File myFile = new File(uri.getPath());
String filePath = myFile.getAbsolutePath();
// now read file store at 'filePath' from local storage (this part is fine)
} else if (GOOGLE DRIVE FILE) {
// what do I do here to retrieve the selected file?
}
}
super.onActivityResult(requestCode, resultCode, data);
}
When selecting a Google Drive file using the Google Drive file picker, the selected file seems to be downloaded (but to where??) but then nothing happens... how do I access the downloaded file to use in my app?
And in onActivityResult() above, how do I tell if the selected file is actually a Google Drive file, so that I can treat it accordingly? i.e. what is the test for if (GOOGLE DRIVE FILE)?
I believe you're using OpenFileActivityBuilder when Drive is selected as the file picker. It will return a EXTRA_RESPONSE_DRIVE_ID which is the Drive ID of the selected file. After this, you just need to call DriveFile.open to open the said file. This can be your flag to check if you selected a Drive File picker or not.
I cannot answer where the file is downloaded, but my best guess for it is its inside your data/ of the application (which you most likely don't have access to)
Android 5 introduce new API for working with SD-card. If you want to write files into some directory on the SC-card, y need get access to it.
As far as I know, it could be done in this way:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);
and then:
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode == RESULT_OK) {
Uri treeUri = resultData.getData();
getActivity().getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
This code launch folder picker, and when folder is picked it requires permissions on the folder by it's Uri.
But is it possible to get permissions on exact directory by it's path? Without using folder picker. I meant that I know directory, which I want get access, and I don't want to launch Folder Picker, because it will confuse user.
How to open DocumentFile without user interaction?
It's impossible. User should choose directory using android's file picker. The only way to get access to folder is using:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);
Because file is provided by Storage Access Framework DocumentsProvider. Actually it could return some Uri from 3-rd party app, which is not real file, but some entity, which 3-rd party app present as file.
I'm trying to get a file object using an intent requesting an openable content.
So I will get from the intent a content URI string.
And I'm trying to figure out how to get a file object from it.
I saw example how to transform it to a file descriptor but haven't seen a way to make it into a file.
The result part which is required to send a File object to a method:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == EXPORT_PICKFILE_RESULT_CODE) {
if (resultCode == RESULT_OK) {
// Export File Chooser returned a result
Uri returnUri = data.getData();
File parmeter = ???? <=== getContentResolver().openFileDescriptor(returnUri, "r");
The request for the File:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(
Intent.createChooser(intent, "Select a File"),
EXPORT_PICKFILE_RESULT_CODE);
I'm Trying to Get a File object using an intent requesting an openable content.
That is not possible in any reliable fashion.
This is akin to saying "I'm trying to get a File object using an HTTP URL from a Web server". While the Web server has a file, you do not have direct file server access to that server. You can get a stream, and create your own file out of that stream.
Similarly, with a ContentProvider, a Uri is not a File. Whether you use openInputStream() on ContentResolver, or get the FileDescriptor and open a stream on that, you need to consume the content as a stream.
After all, there is no requirement that the content be stored in a regular file in the first place, let alone one that you can access. The content may be in an encrypted file, in a BLOB column in a database, etc.
My ultimate goal is to allow the user to select a folder to save a file to - the file is a video file that will be created at some point after the user has chosen the destination.
I am simply using the storage access framework picker to allow them to select a location for it to be saved in.
First of all, is there a way to allow a user to select only a folder (and not a file/filename)?
The best I can do right now is use the ACTION_CREATE_DOCUMENT Intent in order to get a save location, however I do not really want to specify the filename in the SAF picker (this will be done back in the app)...
Secondly, after reading the Storage Access Framework documentation, and cobbling together some bits from a few code samples, I've got a working DocumentsProvider which almost does what I want - which is to allow the user to browse their external storage (SD Card) directories for a suitable place to save a video file - by adding my own root which points to Environment.getExternalStorageDirectory() to the queryRoots() method.
However, what I really want is for that to be my only root (at the minute I've also got Drive, Downloads etc.).
Is it possible to remove/hide other roots so it essentially becomes an application-specific file picker?
Or even show local storage only (perhaps the Root.FLAG_LOCAL_ONLY flag can help)?
Thanks!
API 21 supports Intent.ACTION_OPEN_DOCUMENT_TREE. This allows you to select the location once and then you can use the provided URI to manipulate its content.
private static final int LOCATION_CHOOSER_REQ_CODE = 4;
public void chooseLocation() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, LOCATION_CHOOSER_REQ_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == LOCATION_CHOOSER_REQ_CODE && resultCode == Activity.RESULT_OK) {
if (data != null) {
Uri uri = data.getData(); // Use this URI to access files
}
}