I am building a photo vault (that helps users hide photos) for Android 11 onwards since the birth of mighty "scoped storage" I am able to hide photos by just moving them to the app's private directory.
Now the problem arises when I want to share an image without moving it to the public (shared) directory.
I followed the implemented FileProvider, which converts the image path to URI but when I share the content URI via an intent, the following error pops up in logcat and the receiver application can not read the image.
Permission Denial: reading androidx.core.content.FileProvider uri content://com.androidbull.incognito.vaultreborn.provider/photos/Screenshot_20211221-105658.jpg from pid=10376, uid=1000 requires the provider be exported, or grantUriPermission()
Here is the code I wrote to perform sharing
val imagePath = File(this.filesDir, "photos")
val newImageFile = File(imagePath, currentImage.imageName)
val imageUri = FileProvider.getUriForFile(this, "$packageName.provider", newImageFile)
//
val shareContentIntent = Intent(Intent.ACTION_SEND)
shareContentIntent.data = imageUri
shareContentIntent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
startActivity(Intent.createChooser(shareContentIntent, null))
and just in case you are wondering here is the my provider in AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
And finally the file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="photos"
path="photos/" />
</paths>
Can anyone please help me here?
Thank you :)
Okay, so it turned out that there was a bug in my head, not in the code.
I was only testing it for the very first image that I moved into my vault, somehow this image was showing perfectly but had a file size of 0 bytes, so that was the problem. I tested other images and worked perfectly.
Thanks, everyone for paying attention :)
Related
The app needs to share a PDF file stored in the root of the cacheDir with other apps. The issue is seen on Android 12, possibly other versions too.
Manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Provider paths:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="." />
</paths>
Intent:
val pdfFile = File(requireContext().cacheDir, pdfFileName)
val fileUri: Uri = FileProvider.getUriForFile(
requireContext().applicationContext,
requireContext().packageName.toString() + ".provider",
pdfFile
)
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "application/pdf"
startActivity(Intent.createChooser(intent, "Share Document"))
The share sheet successfully opens but this exception always shows at that point and subsequently sharing to another app fails.
Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading
androidx.core.content.FileProvider uri
content://uk.co.packagename.provider/cache/8BEDF7212-0DE46-42B0-9FA9-32C434BDD2F3HO.pdf
from pid=15363, uid=1000 requires the provider be exported, or grantUriPermission()
The provider as a whole cannot be exported and the URI permission appears to already be granted. I've read through the Android file sharing docs and many S/O answers but I cannot see what needs correcting, can you?
One of the limitations of FileProvider.getUriForFile() is that it does not check to see if the file exists. There are legit reasons for getting a Uri to a file that does not exist, such as for ACTION_IMAGE_CAPTURE. Still, it means that just getting the Uri is no guarantee that that the Uri is useful for reading content.
Compounding that problem is that "does the file exist" via exists() feels like it may be a bit dicey, especially for external storage.
So, it's pretty important to make sure that you have the right File object, and that it should point to an already-existing file, before you call getUriForFile().
After checking what package caused this exception it turned out to be android system. So after granting uri permission, I see no more exceptions like this in the logs:
requireContext().grantUriPermission("android", fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
The reason a security exception ("requires the provider be exported, or grantUriPermission()") is thrown is because the android system is trying to access the shared resource to give the user a preview of what would be shared.
The best approach is to use the intent.setClipData to share the resource in addition to intent.putExtra(Intent.EXTRA_STREAM, fileUri)
The code should look like this:
intent.action = Intent.ACTION_SEND
intent.setClipData(ClipData.newRawUri("", fileUri))
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
#skvalex's answer above works, but it is a hacky way of giving the Android system access to the file for preview in the application share picker screen.
For a additional reference on this, please take a look at the FileProvider class as well.
My App is creating a PDF and passes it to other Apps to be displayed elsewhere. I create the file in internal storage to have to ask the user for less permissions.
I create my intent via:
Intent viewIntent = new Intent(Intent.ActionView);
Java.IO.File document = new Java.IO.File(filePath);
Android.Net.Uri contentUri = FileProvider.GetUriForFile(
_context,
_context.PackageName + ".provider",
document);
viewIntent.SetDataAndType(contentUri, GetMimeType(document));
viewIntent.SetFlags(ActivityFlags.NewTask);
viewIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
viewIntent.AddFlags(ActivityFlags.ClearTask);
viewIntent.AddFlags(ActivityFlags.GrantPersistableUriPermission);
viewIntent.AddFlags(ActivityFlags.GrantPrefixUriPermission);
viewIntent.AddFlags(ActivityFlags.GrantWriteUriPermission);
Intent chooser = Intent.CreateChooser(viewIntent, "");
chooser.SetFlags(ActivityFlags.NewTask);
chooser.AddFlags(ActivityFlags.GrantReadUriPermission);
chooser.AddFlags(ActivityFlags.ClearTask);
chooser.AddFlags(ActivityFlags.GrantPersistableUriPermission);
chooser.AddFlags(ActivityFlags.GrantPrefixUriPermission);
chooser.AddFlags(ActivityFlags.GrantWriteUriPermission);
_context.StartActivity(viewIntent);
On the Google Pixel 3 XL where I test, I can open a PDF without any issues.
When I do the same on a Huawei tablet with API level 24, sometimes everything works but at other times Adobe Acrobat shows an error: This file could not be accessed. Check the location or the network and try again.
The behavior isn't deterministic, sometimes I get the error but other times everything works fine.
In the Application node of your Android Manifest make sure you've added a FileProvider definition:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.FileProvider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/provider_paths" />
</provider>
Add a Resources/xml/provider_paths file with the contents:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
You can restrict this later if needed. However, for my use case I put files in external storage, which I share from. You need to adjust this accordingly where you are sharing from.
I.e. if you are sharing the file from internal app storage you will need to add a files-path definition there too.
Then when sharing a file you simply do:
var packageName = context.ApplicationInfo.PackageName;
var fileProviderName = $"{packageName}.FileProvider";
var intent = new Intent(Intent.ActionSend);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
intent.SetType("image/*"); // change mime type if wanting to open in other app
intent.PutExtra(Intent.ExtraStream,
FileProvider.GetUriForFile(context, fileProviderName, new Java.IO.File(filePath)));
StartActivity(intent);
That should be enough, works fine every time for me for sharing images to another app. I don't think you need the flags for your chooser Intent, only for the inner viewIntent. Also the GrantReadUriPermission should be the only thing needed if you are providing flags.
I am trying to share an image via classic Intent. I have added the following items:
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_images" path="." />
</paths>
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And finally MainActivity.java:
private void shareFile(String fileName) {
Intent share = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getApplicationInfo().dataDir + "/app_flutter/userphotos", fileName));
share.setData(uri);
share.setType("image/png");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(share, "Share"));
}
The problem I am facing is that image I am trying to share has the following path:
/data/user/0/shoedrobe.innovativeproposals.com.shoedrobe/app_flutter/userphotos/receipt20181101094430.jpg
However the FileProvider is trying to access it from here:
java.lang.IllegalArgumentException: Failed to find configured root
that contains
/data/data/shoedrobe.innovativeproposals.com.shoedrobe/app_flutter/userphotos/receipt20181101094430.jpg
For saving images I am using package path_provider and I am saving items under
getApplicationDocumentsDirectory(), which in Android is AppData directory.
I am not sure why FileProvider decided suddenly to go from /data/user/0/ to /data/data/ folder, therefore any help or tips regarding this matter would be highly appreciated.
Update:
I have updated the code as per recommendations and replaced the Uri under MainActivity.java with the following line:
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getDir("flutter", Context.MODE_PRIVATE).getPath() + "/app_flutter/userphotos", path));
Nonetheless, the problem still persists (same exception that file is supposed to be under /data/data/<package> instead of /data/user/0. I have also tried to add additional persmissions to my AndroidManifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
but it did not work as well. Could the problem lie with my file_paths.xml file?
In the end, none of the proposed solutions did not work; neither using extenal-path, nor the other solution.
The way how I managed in the end to share file was to actually copy it to a temporary (cache) directory first (in flutter path_provider it's getTemporaryDirectory()) and update file_paths.xml to following:
<cache-path name="image" path="."/>
and finally under MainActivity.java:
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getCacheDir(), path));
Under the hood, Flutter's getApplicationDocumentsDirectory() is using getDir() instead of dataDir, which could be different.
From the Android documentation on getDataDir(), which is equivalent to this.getApplicationInfo().dataDir:
Returns the absolute path to the directory on the filesystem where all private files belonging to this app are stored. Apps should not use this path directly; they should instead use getFilesDir(), getCacheDir(), getDir(String, int), or other storage APIs on this class.
The returned path may change over time if the calling app is moved to an adopted storage device, so only relative paths should be persisted.
Therefore, to ensure consistency between the directory used for saving images by Flutter and the directory used to retrieve the files via FileProvider, the getUriForFile line in MainActivity.java could be modified like this:
FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(<your application context>.getDir("flutter", Context.MODE_PRIVATE).getPath() + "/app_flutter/userphotos", fileName));
... replacing <your application context> with the variable storing your application's context, if any.
I'm currently developing an application which creates an excel file(with apache poi) from the sqlite database in android. Everything is working well, except that I can't send the file to another app.
I implemented an fileprovider so I other apps can have permission to my file:
AndroidManifest.xml:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.ria.masterdetailnoten"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"></meta-data>
</provider>
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="backup" path="backup/"/>
</paths>
ExcelFile.java:
Uri uriFile = FileProvider.getUriForFile(ct, "com.example.ria.masterdetailnoten", backupFile);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setDataAndType(uriFile, "application/vnd.ms-excel");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.createChooser(intent, "Choose");
ct.startActivity(intent);
I know for sure that my file is being created and I can read it again with Apache POI. So the only things which doesn't work is sending the intent to another app.
As an example if I choose Google Drive in the intent chooser then following error appears:
"Upload failed: request contained no data."
If I open it with the Mailbox App following error appears:
"Mailbox could not read the selected file"
I hope you can help me guys, I appreciate your help!
Have you tried using: putExtra(Intent.EXTRA_STREAM, uriFile) instead of: setData(AndType)?
I've managed to work this out with intent.selector = Intent(Intent.ACTION_SEND).apply { data = Uri.parse("file:") }
I am implementing a File Provider, followed the doc carefully but unable to use the files in the app itself or share any file.
Added manifest, created xml file for dir sharing, generating content uri, granting uri permission, tried sharing files with cache, external, internal dir but nothing is working.
Searched the net but found nothing which is missing in the code.
Below is the code:
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="package.widget.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
filepaths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="widgetDoc" path="."/>
</paths>
GetUri:
private Uri getFileUri(Context context, String name){
File newFile = new File(context.getCacheDir() + File.separator + StorageUtil.INTERNAL_DIR, name);
Uri contentUri = FileProvider.getUriForFile(context, "package.widget.fileprovider", newFile);
return contentUri;
}
Code to access pdf file:
Intent target = new Intent(Intent.ACTION_VIEW);
Uri uri = getFileUri(getApplicationContext(), file);
target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
target.setDataAndType(uri,"application/pdf");
Intent intent = Intent.createChooser(target, "Choose Pdf Viewer...");
startActivity(intent);
Code to access image:
imageview.setImageURI(getFileUri(getApplicationContext(), file));
Kindly help me out where I am going wrong, not even able to use these files in my own app too.
Thanks in advance.
I need to show these image files in a widget and now if am accessing the image files from the widget then it is giving IllegalArgumentException: Failed to find configured root however it is working fine with activity
Ideally, use setImageViewBitmap(), and make sure that your image is small (under 1MB in heap space, such as less than 512x512).
While the RemoteViews can accept a Uri, you have good way of associating Intent.FLAG_GRANT_READ_URI_PERMISSION with a RemoteViews for the purposes of populating an ImageView. And, you cannot export your FileProvider, as FileProvider does not allow that.
You can attempt to identify the user's chosen home screen and use grantUriPermission() to grant the home screen access to this Uri, but I would expect that solution to be fragile.