I know that we can open a file normally from internal storage like this:
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "my_file.zip");
Uri uri = (FileProvider.getUriForFile(context, AUTHORITY_OPEN_FILE, file)
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(uri);
context.startActivity(intent);
But I do not know on how to open a file from SD card, which we can pick using DocumentFile like this:
Uri uri = new Uri.Builder()
.scheme("content")
.authority("com.android.externalstorage.documents")
.appendPath("document")
.appendPath(directory)
.appendPath(fileName)
.build();
DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri);
I tried the following snippet:
Uri uri = documentFile.getUri();
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(uri);
context.startActivity(intent);
But resulting error:
Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.VIEW dat=content://com.android.externalstorage.documents/document/6331-6132:/haxm-windows_r05_2.zip flg=0x10000001 cmp=com.google.android.gm/.browse.TrampolineActivity } from ProcessRecord{a0ed6d5 9894:com.mypackage.app/u0a169} (pid=9894, uid=10169) requires com.google.android.gm.permission.READ_GMAIL
I did grant for read and write external storage permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And Uri permission as well:
int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(uri, takeFlags);
A file stored in SD card gives us a Uri like this, where 6331-6132 is identifier for our removable SD card:
content://com.android.externalstorage.documents/document/6331-6132:Folder/haxm-windows_r05_2.zip
I read so many posts on StackOverflow, but nothing help. Can you help me to solve this error? Thanks in advance.
yo must add permission in manifest file and for marshmallow or above version of android you add run time permission.
for this view
https://www.simplifiedcoding.net/android-marshmallow-permissions-example/
I get the Uri using Intent.ACTION_OPEN_DOCUMENT. Next, I save the Uri into my database. Finally, I reconstruct the Uri to get stream using getContentResolver().openInputStream(uri).
My guess is that you are not calling takePersistableUriPermission() on a ContentResolver in onActivityResult(), before you save the Uri to the database. Without this, you do not have long-term access to the content identified by the Uri.
This may be very basic but the error message says that you are missing permission
did you ask for READ_EXTERNAL_STORAGE ?
https://developer.android.com/training/data-storage/files#ExternalStoragePermissions
I finally found a solution for this. java.lang.SecurityException: Permission Denial will be thrown when you open a external file using DocumentFile.fromSingleUri(context, uri). This static method is only useful when you pick a file inside onActivityResult() method with action Intent.ACTION_OPEN_DOCUMENT or Intent.ACTION_CREATE_DOCUMENT. When your app is destroyed, the permission to access the file is gone. You should not use DocumentFile.fromSingleUri(context, uri) while working with files that require long time permission. Hence, we need DocumentFile.fromTreeUri(context, uri) instead.
To do that so, you must access the root of SD card's ID (e.g. 6331-6132) first. For example:
String path = "6331-6132:Video/Iykwim.mp4";
String sdcardId = path.substring(0, path.indexOf(':') + 1); // => returns '6331-6132:'
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(sdcardId));
DocumentFile root = DocumentFile.fromTreeUri(context, uri);
// Now, we already have the root of SD card.
// Next, we will get into => Video => Iykwim.mp4
DocumentFile file = root.findFile("Video").findFile("Iykwim.mp4");
Notice that you cannot access the file directly like this Uri:
String path = "6331-6132:Video/Iykwim.mp4";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(path));
DocumentFile file = DocumentFile.fromTreeUri(context, uri);
Or like this:
String folder = "6331-6132:Video";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(folder));
DocumentFile directoryVideo = DocumentFile.fromTreeUri(context, uri);
DocumentFile file = directoryVideo.findFile("Iykwim.mp4");
Finally, you can open the file using Intent.ACTION_VIEW:
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(documentFile.getUri());
context.startActivity(intent);
The worst scenario is when you call findFile() from a folder with so many files inside. It will take long time and may lead to crash your app. Ensure that you always call this method from different thread.
Android is getting worst day by day. They have made a Storage Access Framework (SAF) that makes you difficult to manage files on SD card. Using java.io.File is almost deprecated since Android Kitkat. You should use DocumentFile instead.
Related
I am trying to upgrade an app to be copliant with the Android11 shared storage concept. This app needs to store its created gpx files in a storage area not destroyed in case the app is uninstalled. This area shall stay in shared storage and the directory can be created with ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType(DocumentsContract.Document.MIME_TYPE_DIR);
intent.putExtra(Intent.EXTRA_TITLE, "AGPS-Tracker");
startActivityForResult(intent, SS_CREATE_GPX_DIR_REQUEST);
and in onActivityResult:
Uri uri = data.getData();
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
startDirUri = DocumentsContract.buildTreeDocumentUri(uri.getAuthority(),DocumentsContract.getDocumentId(us));
Then I try to use startDirUri :
uriDir = DocumentsContract.buildDocumentUriUsingTree(startDirUri, DocumentsContract.getTreeDocumentId(startDirUri));
I get: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3AAGPS-Tracker%20(11)/document/primary%3AAGPS-Tracker%20(11)/children from pid=7209, uid=10341 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
Then if I use ACTION_OPEN_DOCUMENT_TREE on this new created directory, finally I get the access.
My question is: why a directory created by the user can't be immediately used? Really we need to ask twice the user: first to create and then to use the same directory?
I am creating an android application using xamarin.android with targetsdk is Marshmallow.
I am using SAF for writing to external sd card (removable media), for this I call an Intent with ACTION_OPEN_DOCUMENT_TREE
var intent = new Intent(Intent.ActionOpenDocumentTree);
StartActivityForResult(intent, ACTION_OPEN_DOCUMENT_TREE);
I had saved that tree uri on SharedPreference for long term permission until my device gets rebooted like
treeUri.ToString()
and read that uri like
var str = Android.Net.Uri.Decode(UriString);
var TreeUri = Android.Net.Uri.Parse(str);
And parse this TreeUri to DocumentFile class to manage documents but sometimes in between it missed some part of uri and I need to invoke it again to user to grant permission for managing documents and save the treeuri again.
As we can see this on ES File Explorer that it also ask the user to grant the permission and it maintains the treeuri until the device reboots.
Here is the image of ES File Explorer
Do correct me what to do to maintain tree uri until device reboots.
Edit:
Here is the TakePersistableUriPermission code:
var takeFlags = ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission;
GrantUriPermission(PackageName, data.Data, takeFlags);
ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
I'm trying to update my code which gets the database file. I'm trying to use a FileProvider instead.
I'm a bit stuck because the original file path is "/storage/emulated/0/Android/data/com.abc/files/abcMobile.db3”
I need to get the URI which is returned by FileProvider.GetUriForFile to include "/storage/emulated/0/“ at the start of it.
Here is my code:
Log.Info ("directory", GetDirectory (dir).ToString());
Log.Info ("filename", filename);
File newFile = new File (GetDirectory (dir).AbsolutePath, "/" + filename);
Android.Net.Uri androidContentUri = FileProvider.GetUriForFile (Application.Context, Application.Context.PackageName + ".fileprovider", newFile);
System.Uri systemContentUri = SystemUri (androidContentUri);
return systemContentUri;
In the code above I have created a file which has a path of "/storage/emulated/0/Android/data/com.abc/files/abcMobile.db3” but when I pass the file to FileProvider.GetUriForFile() It seems to create an Android.Net.URI of {content://com.abc.fileprovider/./Android/data/com.abc/files/abcMobile.db3} and I cast it to a System.Net.URI which becomes {content://com.abc.fileprovider/Android/data/com.abc/files/abcMobile.db3}.
I just need to figure out why it deletes "/storage/emulated/0/“ from the start of the Android.Net.Uri and stop that from happening and then it should work. Any ideas?
I just need to figure out why it deletes "/storage/emulated/0/“ from the start of the Android.Net.Uri and stop that from happening and then it should work. Any ideas?
Because you are generating content uri for your file,which grant temporary permission for access to this file. Return a full path of the file is not considered to be a secure way.
And if you are refer to "Generating the Content URI for a File" section of FileProvider. You will find the following sentence:
To share a file with another app using a content URI, your app has to generate the content URI.
...
As a result of the previous snippet, getUriForFile() returns the content URI content://com.mydomain.fileprovider/my_images/default_image.jpg.
So, it is a expected form of ContentUri that returned by FileProvider.
When opening a file from a file explorer I get a content scheme URI like following:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk
I then make a temporary copy of the file using the content resolver using something like following:
File tempFile = getFileFromContentUri(getContext(), mUri, null);
After the app processed tempFile this file gets deleted. The problem is now that I want to forward mUri to another activity with following code, but I am getting a security exception while doing so, so it seems that the URI can only be used once, is this right?:
private forwardFile(Uri fileUri) {
final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
Uri uri;
if (ContentResolver.SCHEME_CONTENT.equals(fileUri.getScheme())) {
uri = new Uri.Builder()
.path(fileUri.getPath())
.authority(fileUri.getAuthority())
.scheme(ContentResolver.SCHEME_CONTENT)
.build();
}
installIntent.setData(uri);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(installIntent);
}
Is there the possibility to reuse the uri or a workaorund that you can see? I'm not seeing a way I can handle this, e.g. I must delete the temp file, but if I forward the copied file instead of the original URI I don't get a callback so I wouldn't know when to delete the copied file.
And here the exception I am getting:
java.lang.SecurityException: Uid 10165 does not have permission to uri 0 # content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk
Only if the serving app defines a persistent permission and the client calls context.getContentResolver().takePersistableUriPermission() this may work, but I am also not sure.
You got the permission for:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk
No wonder that you got a java.lang.SecurityException: using a completely different (and non existing) file:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/Download/testfile.apk
If I grant READ_EXTERNAL_STORAGE permission all works good, but can I get it to work without asking? It worked without It on my emulator, but on a real device it doesn't work without it.
Output stream
String fileName = "Körjorunal_" + lastUpdateTime + ".PDF";
String path = Environment.getExternalStorageDirectory().toString();
File file = new File(path, fileName)
pdfUri = Uri.fromFile(file);
OutputStream outputStream = new FileOutputStream(file);
document.writeTo(outputStream);
document.close();
outputStream.close();
Intent
// Gets invoked on setup,
mShareIntent = new Intent();
mShareIntent.setAction(Intent.ACTION_SEND);
mShareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
mShareIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Creates document type.
mShareIntent.setType("application/pdf");
// Going via email:
mShareIntent.putExtra(Intent.EXTRA_SUBJECT, "Körjournal i bifogad PDF");
// Attach the PDf Uri.
mShareIntent.putExtra(Intent.EXTRA_STREAM, pdfUri);
mShareActionProvider.setShareIntent(mShareIntent);
Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
FLAG_GRANT_*_URI_PERMISSION (for READ and WRITE) really are for where the Uri is from your own ContentProvider. If you want to avoid the need for the WRITE_EXTERNAL_STORAGE (and superfluous READ_EXTERNAL_STORAGE) permission:
Step #1: Write the file to internal storage, such as getCacheDir()
Step #2: Serve that file from internal storage using FileProvider
Step #3: Use your existing ACTION_SEND logic, substituting the Uri that you get from FileProvider
You will see that pattern used in this sample project. My sample is for ACTION_VIEW, not ACTION_SEND, but otherwise it is a close match.
One key difference, though, will be in how you grant the permissions, if your minSdkVersion is below 21. Uri values in extras were ignored by FLAG_GRANT_*_URI_PERMISSION on Android 4.4 and below. For those versions, the only solution that I know of is to manually grant rights to every possible app (in your case, every PDF viewer).
In this sample app, I use ACTION_IMAGE_CAPTURE to take a picture, and I use a FileProvider Uri for that (since file Uri values are going away). Just as you are using EXTRA_STREAM with a Uri, I am using EXTRA_OUTPUT, and so I run into the same Uri-in-an-extra permission problem. And, so, I go through all this mess:
i.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) {
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
else {
List<ResolveInfo> resInfoList=
getPackageManager()
.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, outputUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
startActivityForResult(i, CONTENT_REQUEST);
(here, i is my ACTION_IMAGE_CAPTURE Intent)
So, I grant any ACTION_IMAGE_CAPTUREIntentthe rights to work with thisUri` on Android 4.4 and older.
You cannot use the feature without the permission.
If you are using an Android version prior to Marshmallow, you can just add the permission to the Manifest and it won't ask for it again.
But Marshmallow categorized permissions into Normal and Dangerous permissions. You have to ask for dangerous permissions just before the user uses the feature.