In my app I want to give users a way to pick a file from the app’s data directory. This is my code:
// use ACTION_OPEN_DOCUMENT because ACTION_GET_CONTENT will give us
// gallery and other stuff we don’t need
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
Uri uri = Uri.parse(getExternalFilesDir(null).getAbsolutePath());
Log.d(TAG, "Browsing " + uri.toString());
intent.setDataAndType(uri, "*/*");
// show the entire internal storage tree
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
The logcat shows me that the URI that I am setting is file:///sdcard/Android/data/my.app/files, but the file picker UI defaults to the shared storage root (/sdcard).
The following code works (requires API 26+ as per the documentation, the intent is available from the API as DocumentsContract.EXTRA_INITIAL_URI):
// works only with this intent, at the expense of gallery etc. appearing
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// apparently we need a valid content URI
Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmy.app%2Ffiles");
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
Log.d(TAG, "Browsing " + uri.toString());
intent.setType("*/*");
// show the entire internal storage tree
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
However, ACTION_GET_CONTENT causes all kinds of providers to appear, such as Gallery and Music, when all I need is the local file system (and in fact just the app’s private subtree). If I change the intent to ACTION_OPEN_DOCUMENT, the URI I supply is ignored.
How can I get the file picker UI to start in a directory of my choice, with only a minimal choice of content providers?
Edit: Testing this on Anbox, which I just realize is only at API 25—in fact, I need a way that works on APIs as low as 24.
There may not be a universally viable solution, but the following has worked on some builds (though not on others):
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmy.app%2Ffiles");
intent.setData(uri);
intent.setType("*/*");
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);
uri must be a content URI for the com.android.externalstorage.documents provider. The URI path is /document/primary%3A, followed by the filesystem path to the folder to start in. The path must be relative to the shared storage root (i.e. drop the leading /sdcard/ or equivalent on the device and make sure the result does not start with a slash) and escaped.
The call to Intent#setData() does not help in setting the default location (unlike with some third-party file managers) but prevents unwanted storage providers (such as Gallery and Music) from being displayed.
The android.provider.extra.INITIAL_URI extra sets the initial URI, but this may not work prior to API 26 (although it does work on some flavors of Android).
The android.content.extra.SHOW_ADVANCED extra causes device storage to be available as a provider (otherwise, depending on the flavor of Android, it may require the user to select it or not be available at all).
Again, still not a perfect solution but the closest I managed to get.
Related
Apparently the "file picker" app does not completely implement the ACTION_OPEN_DOCUMENT_TREE intent.
Let's say this code is executed:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setType("vnd.android.document/directory");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
This error is issued:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT_TREE cat=[android.intent.category.OPENABLE] typ=vnd.android.document/directory flg=0x43 }
the same with this alternative type "application/vnd.google-apps.folder"
or not including the line where the OPENABLE category is added
or using intent.setType(DocumentsContract.Document.MIME_TYPE_DIR);
while if this code is executed:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
there are no errors but only disk roots are available for the user to choose, so the cloud space is not showed.
If the ACTION_OPEN_DOCUMENT is used like this:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, OPEN_FILE_REQUEST_CODE);
cloud roots (only of Drive cloud domain, the only app that implements SAF, I think) are available and files can be selected.
I experimented and the Storage Access Framework is implemented. See for example this question:
Storage Access Framework - failing to obtain document tree from uri (returned from Drive app)
where you can see that the functionality exists, altough it is sort of a hack and refers to creating a folder, not selecting it (a folder should be a "document tree").
I think I have to find the right mime type so what could it be? I saw that many times there are erros when the intent has some wrong type or flag but when the right parameters are found it works.
Otherwise, how to officially ask Google to make the "file picker" app of SAF correctly implement the ACTION_OPEN_DOCUMENT_TREE intent for cloud domain, created by themselves for their own Storage Access Framework on Android?
I hope it is already implemented, in fact.
I'm trying to open a specific folder using intent, but the device's recent folder open instead.
Code:
Uri uri = Uri.parse(filepath);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT,uri);
intent.setDataAndType(uri,"text/plain");
StartActivityForResult(intent, REQUEST_CODE);
ACTION_GET_CONTENT does not take a Uri, so yours is ignored.
With ACTION_OPEN_DOCUMENT, you can use EXTRA_INITIAL_URI to provide some document tree (e.g., from ACTION_OPEN_DOCUMENT_TREE) that should be used as the initial location.
I am trying to read pdf files in the android app.
Fire following intent to get URI.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
startActivityForResult(intent, PDF_FILE_SELECTOR_INTENT_ID);
Problem is, the Downloads folder also shows old files that I have deleted.
Also, when I select those files a valid URI is returned in onActivityResult(). When I create File from URI and check exists() it returns false which makes sense as I have already deleted the file from the Downloads folder.
How can I make sure that the Downloads folder shown on ACTION_GET_CONTENT shows only files which are currently present and not deleted ones?
Thanks.
Instead of
intent.setType("application/pdf"); use the Intent.EXTRA_MIME_TYPES in the putExtra
In Java:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
"application/pdf", // .pdf
});
startActivityForResult(intent, REQUEST_CODE);
In Kotlin
startActivityForResult(
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(
"application/pdf", // .pdf
))
},
REQUEST_CODE
)
Update: Actual Download Folder & "Downloads" folder different in this case.
Download is for your actual downloaded files
Downloads is a history folder behaves like a short-cut and it's not cleared automatically when the actual file pointed to is manually removed. This might be most likely an expect behavior.
In your case , you need to hide the downloads folder ( still you can use Download). using this line of code will show "Internal storage" by default:
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
Call addCategory(Intent.CATEGORY_OPENABLE) to your Intent as recommended here: https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT
Currently I have the following code that allows a user to choose an image.
int requestCode = 1337;
Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooserIntent.setType("image/*");
chooserIntent = Intent.createChooser(chooserIntent, "Please choose a picture");
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(chooserIntent, requestCode);
My question is:
does Android guarantee that the returned Uri is always pointing to a location on disk, or is it possible that it might be pointing to somewhere on the internet too?
P.S. although I am not sure about this, the Uri returned by this piece of code seems to always start with content:// - I am not sure whether or not this holds for all possible return values, I thought I would just add this here to help out any possible question answerers.
does Android guarantee that the returned Uri is always pointing to a
location on disk, or is it possible that it might be pointing to
somewhere on the internet too?
It is possible to have Uri other than local disk i.e. it can be remotely as well. You will get URL from remote then convert it to Uri and use it.
From official docs:
An ACTION_GET_CONTENT could allow the user to create the data as it
runs (for example taking a picture or recording a sound), let them
browse over the web and download the desired data, etc.
Convert Url to a Uri (Reference):
Uri uri = Uri.parse( "http://www.facebook.com" );
In my application I download an apk from a server and save it on the device. This apk is used to update the app. If the download is finished the user is prompted to do so by using the following code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", updateAPK);
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
} else {
Uri apkUri = Uri.fromFile(updateAPK);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
This is working like it should, but my question is: Is ist possible to get the result of the intent, so I can check whether the user cancelled the installation or not?
Divide your scenario into three scenarios, not two.
On devices older than API Level 14, use ACTION_VIEW with a file Uri. Note that you do not need FLAG_ACTIVITY_NEW_TASK — or, more accurately, you should be consistent in either using it or not using it across all three scenarios.
On devices that are API Level 14-23, use ACTION_INSTALL_PACKAGE with a file Uri. Set EXTRA_RETURN_RESULT to true, and use startActivityForResult().
On devices that are API Level 24+, use ACTION_INSTALL_PACKAGE with a content Uri, as you are doing. Set EXTRA_RETURN_RESULT to true, and use startActivityForResult().
In those latter two scenarios, onActivityResult() will report whether the user installed the app (RESULT_OK) or not.