I am trying to send a bitmap from the cache directory of my app to another external application by granting temporary read permissions through a file provider. When I select the messaging application on my phone (package name : "com.android.mms") the messaging applications crashes and I get the error:
java.lang.SecurityException: Permission Denial: reading android.support.v4.content.FileProvider uri content://com.example.brandon.emojimms2/shared_images/image.png from pid=9804, uid=10024 requires the provider be exported, or grantUriPermission()
Here is a screenshot of the entire error printout if needed:
I only get this error when selecting com.android.mms from the intent chooser. Every other application that I choose sends the bitmap without error. Even the messaging system on newer phones (com.google.android.apps.messaging) are able to send the bitmap with the fileprovider without any errors. I checked this with several emulated phones and apps, and the results always come out the same. The only app that I found that has a problem with file provider is "com.android.mms".
Here is the code where I share the intent:
private void shareImage()
{
File imagePath = new File(mContext.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(mContext, "com.example.brandon.emojimms2", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, mContext.getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
}
I even tried a solution recommended in another stackoverflow post that suggested to grant each individual intent activity write and read uri permissions, but that also did not work.
Related
I am currently adapting my app to Android 11.
When I want to share a file, I get the following error message in logcat:
Permission Denial: reading androidx.core.content.FileProvider uri
content://... from pid=20333, uid=1000 requires the provider be
exported, or grantUriPermission()
But my app is still working and sharing works without problems.
The problem only occurs when I use:
startActivity(Intent.createChooser(intent, "share"));
It does not occur when I use:
startActivity(intent);
In the AndroidManifest.xml of course I set android:requestLegacyExternalStorage to true. targetSdkVersion is 29.
Uri uri = FileProvider.getUriForFile(this, AUTHORITY_FILE, file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, "subject");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "share")); // error
startActivity(intent); // no error
Use ShareCompat.IntentBuilder. It is doing all the right things for you.
For the underlying issue, see https://issuetracker.google.com/issues/173137936
Uri uri = FileProvider.getUriForFile(context, AUTHORITY_FILE, file);
//TODO: Use the proper MIME type. text/plain is for sharing text via EXTRA_TEXT!
// If the file actually contains text, use the type "text/*".
new ShareCompat.IntentBuilder(context)
.setType("text/plain")
.addStream(uri)
.setSubject("subject")
.setChooserTitle("share")
.startChooser();
We are building a quiz instant app, where the user can complete the quiz and then share their result. We share some text with a link, and also an image that shows the user's quiz result. There is no problem when we go through this flow in the installed app, however in the instant app, the image fails to share.
Here is how we generate the intent:
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", image)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, content)
putExtra(Intent.EXTRA_STREAM, uri)
type = "image/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
activity?.startActivity(Intent.createChooser(shareIntent, getString(R.string.quiz_share_title)))
Here is the provider in our base application manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/fileprovider" />
</provider>
When the user shares the image in the instant app, this error message appears in logcat:
java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.redacted.fileprovider/shared/1563809004297.png from pid=29184, uid=1000 requires the provider be exported, or grantUriPermission()
I have tried setting exported="true", and that crashes the instant app on startup with the following exception:
java.lang.RuntimeException: Unable to get provider androidx.core.content.FileProvider: java.lang.SecurityException: Provider must not be exported
I'm guessing that instant apps can't use the FLAG_GRANT_READ_URI_PERMISSION flag, for the same reason that they can't use the WRITE_EXTERNAL_STORAGE permission.
Is there another way we can share images in instant apps?
An instant app can not have an exported ContentProvider. This is a security restriction and crashing the app here is working as intended.
You could use InstantApps.showInstallPrompt() before firing the Intent in order to get users to install the app before doing this. Please make sure that you display a message containing your rationale or otherwise you might confuse your users.
There are other ways to share images using instant apps. But these depend on where the image is coming from. In case of an external content provider (i.e. the Camera app's) you should be able to forward the URI.
use this piece of code to share image from directory:
private void shareImage() {
Intent share = new Intent(Intent.ACTION_SEND);
// If you want to share a png image only, you can do:
// setType("image/png"); OR for jpeg: setType("image/jpeg");
share.setType("image/*");
// Make sure you put example png image named myImage.png in your
// directory
String imagePath = Environment.getExternalStorageDirectory()
+ "/myImage.png";
File imageFileToShare = new File(imagePath);
Uri uri = Uri.fromFile(imageFileToShare);
share.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(share, "Share Image!"));
}
You can share anythings using Intent class
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "Image URL");
startActivity(intent);
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.
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.
We've been trying to solve this issue for the past few hours, finally decided we should revert to StackOverflow.
Here goes -
We have an application that downloads a PDF file from a server to the app's cache directory and then opens it using the packet manager. Of course it goes through grantUriPermission() to give read (and write) permissions to all available packages.
Though it works great on most devices, today we encountered a device that has POLARIS Office 5 installed as the default PDF viewer.
Whenever we're opening the file, Polaris simply displays a message saying "This document cannot be opened".
I should say that when trying to open the file through Acrobat Reader, it works great.
Also, when we copied the file from the cache directory to an external directory (using Android's File manager) and then opened it in Polaris manually it worked great.
We would have given up on it, but since Polaris is the default viewer on many devices, we really would love solving that problem.
Here is the code -
public void onDownloadDone(String filepath) {
// Set a file object that represents the downloaded file
File file = new File(filepath);
// Set an intent for the external app
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get mime type of the downloaded file
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(filepath);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
intent.setType(mimeType);
// Look for installed packges according to the file's mime type
PackageManager pm = context.getPackageManager();
Uri contentUri = FileProvider.getUriForFile(context, "myapp.fileprovider", file);
// Set the file uri in the intent
intent.setData(contentUri);
// Give permissions to the file to each external app that can open the file
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
for (ResolveInfo externalApp: activities){
String packageName = externalApp.activityInfo.packageName;
context.grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
// Start the activities (or launch the menu) if any activity exists
if (activities.size() > 0){
context.startActivity(intent);
} else {
System.out.println("Warning!!! No app for file " + filepath);
}
}
Thank a lot!
I had exactly the same problem. The PDF files would open with ezPDF and Adobe but not with Polaris Viewer.
You have to set the data and type with:
intent.setDataAndType(uri, "application/pdf");
instead of using:
intent.setType("application/pdf");
intent.setData(uri);
For me the following is working fine with Polaris now:
Uri uri = FileProvider.getUriForFile(MyApplication.getContext(), MyApplication.getContext().getPackageName() + ".myfileprovider", destFile);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);