Related
This question already has answers here:
Permission Denial with File Provider through intent
(4 answers)
Closed 11 months ago.
I am trying to share file with FileProvider. I checked that file is shared properly with apps like Gmail, Google Drive etc. Even though following exception is thrown:
2019-08-28 11:43:03.169 12573-12595/com.example.name E/DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.example.name.provider/external_files/Android/data/com.example.name/files/allergy_report.pdf from pid=6005, uid=1000 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:729)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:602)
at android.content.ContentProvider$Transport.query(ContentProvider.java:231)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:104)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)
provider:
<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_provider_paths" />
</provider>
file_provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
Sharing Intent
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);
if (fileWithinMyDir.exists()) {
intentShareFile.setType("application/pdf");
Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
intentShareFile.putExtra(Intent.EXTRA_SUBJECT, "Sharing File...");
intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
}
Hopefully you can point out my mistake why this exceptions is thrown when it seems like apps are granted permission properly and sharing works as it should be.
EDIT:
I found that the problem lies in line:
startActivity(Intent.createChooser(intentShareFile, "Share File"));
When I changed it simply to
startActivity(intentShareFile);
However it displays a little bit different layout for picking application. But still I cannot figure out why original chooser is not working.
Sorry about the late response. I solved it like this:
Intent chooser = Intent.createChooser(intentShareFile, "Share File");
List<ResolveInfo> resInfoList = this.getPackageManager().queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
this.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
startActivity(chooser);
This helped me: Permission Denial with File Provider through intent
There is also a way to grant the URI permission through the Intent flag, without doing in manually with grantUriPermission() and by keeping the usage of Intent.createChooser().
The Intent.createChooser() documentation states:
If the target intent has specified FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION, then these flags will also be set in the returned chooser intent, with its ClipData set appropriately: either a direct reflection of getClipData() if that is non-null, or a new ClipData built from getData().
Therefore, if the original Intent has the Uri set in its ClipData or in setData(), it should work as wanted.
In your example, the ACTION_SEND Intent supports the Uri set through setClipData() as of Jelly Bean (Android 4.1).
Long story short, here is a working example of your code:
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);
if(fileWithinMyDir.exists()) {
String mimeType = "application/pdf";
String[] mimeTypeArray = new String[] { mimeType };
intentShareFile.setType(mimeType);
Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
// Add the uri as a ClipData
intentShareFile.setClipData(new ClipData(
"A label describing your file to the user",
mimeTypeArray,
new ClipData.Item(uri)
));
// EXTRA_STREAM is kept for compatibility with old applications
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
"Sharing File...");
intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
}
In addition to the answer of Nit above, it is possible to set the ClipData directly with the to be shared uri as follows.
intent.setClipData(ClipData.newRawUri("", uri));
This prevents the security exception when presenting the intent chooser.
If you want to share multiple uris, you can set the clipdata with multiple uris to prevent the security exception. To summarize:
Intent intent = new Intent();
intent.setType(mimeType);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (uris.size() == 0) {
return;
}
else if (uris.size() == 1) {
Uri uri = uris.get(0);
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setClipData(ClipData.newRawUri("", uri));
}
else {
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
ClipData clipData = ClipData.newRawUri("", uris.get(0));
for (int i = 1; i < uris.size(); i++) {
Uri uri = uris.get(i);
clipData.addItem(new ClipData.Item(uri));
}
intent.setClipData(clipData);
}
startActivity(Intent.createChooser(intent, title));
This worked for me.
Intent sharableIntent = new Intent();
sharableIntent.setAction(Intent.ACTION_SEND);
sharableIntent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION );
Uri imageUri = Uri.parse(ImgLoc);
File imageFile = new File(String.valueOf(imageUri));
Uri UriImage = FileProvider.getUriForFile(context, Author, imageFile);
sharableIntent.setType("image/*");
sharableIntent.putExtra(Intent.EXTRA_STREAM, UriImage);
sharableIntent.putExtra(Intent.EXTRA_TITLE, title);
sharableIntent.putExtra(Intent.EXTRA_TEXT, body);
Intent chooser = Intent.createChooser(sharableIntent, "Chooser Title");
chooser.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
context.startActivity(chooser);
createChooser
Intent.createChooser():Builds a new ACTION_CHOOSER Intent that wraps the given target intent, also optionally supplying a title. If the target intent has specified FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION, then these flags will also be set in the returned chooser intent, with its ClipData set appropriately: either a direct reflection of getClipData() if that is non-null, or a new ClipData built from getData()
I'm trying to attach an image using different applications with the following code:
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_TEXT, "Test example")
sendIntent.type = "image/png"
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(logo.absolutePath))
startActivity(sendIntent)
The image that is attached is generated with this code:
// Generate file for application logo
val path = Environment.getExternalStorageDirectory().absolutePath
val logo = File(path, "logo.png")
// If logo doesn't exist
if (!logo.exists())
{
// Create new file
logo.createNewFile()
// Save application logo to new file
val fOut = FileOutputStream(logo)
val image = BitmapFactory.decodeResource(applicationContext.resources, R.mipmap.ic_launcher_round)
image.compress(Bitmap.CompressFormat.PNG, 100, fOut)
fOut.flush()
fOut.close()
}
But when I try to open GMAIL with this intent, only text shows app with an error: Couldn't attach file.
What am I missing?
EDIT
Here is another solution: android.os.FileUriExposedException: file.jpg exposed beyond app through ClipData.Item.getUri()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
From Android N you have to use FileProvider to Obtain Uri.
Please see below example for file sharing.
ArrayList<Uri> files = new ArrayList<Uri>();
File file = new File(<Your File Path>);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(DetailActivity.this, BuildConfig.APPLICATION_ID + ".provider", file);
} else {
uri = Uri.fromFile(file);
}
files.add(uri);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putExtra(Intent.EXTRA_SUBJECT, "Product Sharing");
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_TEXT, "ANY TEXT MESSAGE");
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files);
startActivity(intent);
Place below code in AndroidManifest.xml in Application tag
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
and place filepath.xml file in xml resource directory
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
You cannot send file without file provider. Gmail, for example, doesn't request READ/WRITE_EXTERNAL_STORAGE permissions, so it cannot access your file. You must use file provider with GRANT_READ_URI_PERMISSION. You can read more here
https://developer.android.com/reference/android/support/v4/content/FileProvider
https://developer.android.com/training/secure-file-sharing/setup-sharing
https://developer.android.com/training/secure-file-sharing/share-file
I tried with the following code but it is not attaching the pdf file.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, message);
sendIntent.setType("text/plain");
if (isOnlyWhatsApp) {
sendIntent.setPackage("com.whatsapp");
}
Uri uri = Uri.fromFile(attachment);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
activity.startActivity(sendIntent);
I had this issue where I was trying to open a pdf file from assets folder and I did not work, but when I tried to open from Download folder (for example), it actually worked, and here is an example of it:
File outputFile = new File(Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DOWNLOADS), "example.pdf");
Uri uri = Uri.fromFile(outputFile);
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("application/pdf");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.setPackage("com.whatsapp");
activity.startActivity(share);
Please note If your targetSdkVersion is 24 or higher, we have to use FileProvider class to give access to the particular file or folder to make them accessible for other apps.
Step 1: add a FileProvider tag in AndroidManifest.xml under application tag.
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
Step 2:
then create a provider_paths.xml file in xml folder under res folder. Folder may be needed to create if it doesn't exist. The content of the file is shown below. It describes that we would like to share access to the External Storage at root folder (path=".") with the name external_files.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
step 3: The final step is to change the line of code below in
Uri photoURI = Uri.fromFile(outputFile);
to
Uri uri = FileProvider.getUriForFile(PdfRendererActivity.this, PdfRendererActivity.this.getPackageName() + ".provider", outputFile);
step 4 (optional):
If using an intent to make the system open your file, you may need to add the following line of code:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Hope this will help :)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
pdfUri = FileProvider.getUriForFile(this, this.getPackageName() + ".provider", pdfFile);
} else {
pdfUri = Uri.fromFile(pdfFile);
}
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("application/pdf");
share.putExtra(Intent.EXTRA_STREAM, pdfUri);
startActivity(Intent.createChooser(share, "Share"));
If you are using Intent.createChooser then always open chooser
This works for me kotlin code.
var file =
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"${invoiceNumber}.pdf"
)
if (file.exists()) {
val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, uri)
putExtra(
Intent.EXTRA_SUBJECT,
"Purchase Bill..."
)
putExtra(
Intent.EXTRA_TEXT,
"Sharing Bill purchase items..."
)
}
startActivity(Intent.createChooser(shareIntent, "Share Via"))
}
I have used FileProvider because is a better approach.
First you need to add an xml/file_provider_paths resource with your private path configuration.
<paths>
<files-path name="files" path="/"/>
</paths>
Then you need to add the provider in your manifests
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cu.company.app.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_provider_paths" />
</provider>
and finally in your Kotlin code
fun Context.shareFile(file: File) {
val context = this
val intent = Intent(Intent.ACTION_SEND).apply {
//file type, can be "application/pdf", "text/plain", etc
type = "*/*"
//in my case, I have used FileProvider, thats is a better approach
putExtra(
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
context, "cu.company.app.provider",
file
)
)
//only whatsapp can accept this intente
//this is optional
setPackage("com.whatsapp")
}
try {
startActivity(Intent.createChooser(intent, getString(R.string.share_with)))
} catch (e: Exception) {
Toast.makeText(this, "We can't find WhatsApp", Toast.LENGTH_SHORT).show()
}
}
ACTION_VIEW is for viewing files. ACTION_VIEW will open apps which can handle pdf files in the list.
startActivity(new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(reportFile), "application/pdf")));
I thought the ACTION_SEND intent would mean "send to other app" and not striktly "send somewhere else".
Try adding Intent.setType as follows:-
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, message);
// sendIntent.setType("text/plain");
if (isOnlyWhatsApp) {
sendIntent.setPackage("com.whatsapp");
}
Uri uri = Uri.fromFile(attachment);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.setType("application/pdf");
activity.startActivity(sendIntent);
For sharing text, below you can find a good example, where you can share text with specific number if you would!
public static void openWhatsAppConversation(Activity activity, String number) {
boolean isWhatsappInstalled = isAppInstalled(activity, "com.whatsapp");
if (isWhatsappInstalled) {
Uri uri = Uri.parse("smsto:" + number);
Intent sendIntent = new Intent(Intent.ACTION_SENDTO, uri);
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sendIntent.setPackage("com.whatsapp");
activity.startActivity(sendIntent);
} else {
ToastHelper.show(activity, "WhatsApp is not Installed!");
openMarket(activity, "com.whatsapp");
}
}
Try with following code
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
File pdfFile = new File(Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DOWNLOADS), "Your file");
Uri uri = Uri.fromFile(pdfFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("application/pdf");
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(shareIntent, "Share via"));
go to file manager apps in android
and open it
then go to >>>data>>>data>>>com.whatsapp and then >>>share_prefs
open com.whatsapp_preference.xml file
search and select file >>>>name=document pdf ....< /string > and save this file
after >>>setting>>>>apps>>>>whatsapp>>>>and press force stop
new open whatsapp again and try to send or share your document
I'm looking for a way to correctly share (not OPEN) an internal file with external application using Android Support library's FileProvider.
Following the example on the docs,
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.supportv4.my_files"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_paths" />
</provider>
and using ShareCompat to share a file to other apps as follows:
ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
does not work, since the FLAG_GRANT_READ_URI_PERMISSION only grants permission for the Uri specified on the data of the intent, not the value of the EXTRA_STREAM extra (as was set by setStream).
I tried to compromise security by setting android:exported to true for the provider, but FileProvider internally checks if itself is exported, when so, it throws an exception.
Using FileProvider from support library you have to manually grant and revoke permissions(at runtime) for other apps to read specific Uri. Use Context.grantUriPermission and Context.revokeUriPermission methods.
For example:
//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
As a last resort, if you can't provide package name you can grant the permission to all apps that can handle specific intent:
//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
Alternative method according to the documentation:
Put the content URI in an Intent by calling setData().
Next, call the method Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
or both.
Finally, send the Intent to another app. Most often, you do this by calling setResult().
Permissions granted in an Intent remain in effect while the stack
of the receiving Activity is active. When the stack finishes, the
permissions are automatically removed. Permissions granted to one
Activity in a client app are automatically extended to other
components of that app.
Btw. if you need to, you can copy source of FileProvider and change attachInfo method to prevent provider from checking if it is exported.
Fully working code sample how to share file from inner app folder. Tested on Android 7 and Android 5.
AndroidManifest.xml
</application>
....
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="android.getqardio.com.gmslocationtest"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
</application>
xml/provider_paths
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="share"
path="external_files"/>
</paths>
Code itself
File imagePath = new File(getFilesDir(), "external_files");
imagePath.mkdir();
File imageFile = new File(imagePath.getPath(), "test.jpg");
// Write data in your file
Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);
Intent intent = ShareCompat.IntentBuilder.from(this)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.setAction(Intent.ACTION_VIEW) //Change if needed
.setDataAndType(uri, "image/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
This solution works for me since OS 4.4. To make it work on all devices I added a workaround for older devices. This ensures that always the safest solution is used.
Manifest.xml:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.package.name.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
file_paths.xml:
<paths>
<files-path name="app_directory" path="directory/"/>
</paths>
Java:
public static void sendFile(Context context) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
String dirpath = context.getFilesDir() + File.separator + "directory";
File file = new File(dirpath + File.separator + "file.txt");
Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Workaround for Android bug.
// grantUriPermission also needed for KITKAT,
// see https://code.google.com/p/android/issues/detail?id=76683
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
}
}
public static void revokeFileReadPermission(Context context) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
String dirpath = context.getFilesDir() + File.separator + "directory";
File file = new File(dirpath + File.separator + "file.txt");
Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
The permission is revoked with revokeFileReadPermission() in the onResume and onDestroy() methods of the Fragment or the Activity.
Since as Phil says in his comment on the original question, this is unique and there is no other info on SO on in google, I thought I should also share my results:
In my app FileProvider worked out of the box to share files using the share intent. There was no special configuration or code necessary, beyond that to setup the FileProvider. In my manifest.xml I placed:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.my.apps.package.files"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_paths" />
</provider>
In my_paths.xml I have:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="." />
</paths>
In my code I have:
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("application/xml");
Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));
And I am able to share my files store in my apps private storage with apps such as Gmail and google drive without any trouble.
As far as I can tell this will only work on newer versions of Android, so you will probably have to figure out a different way to do it. This solution works for me on 4.4, but not on 4.0 or 2.3.3, so this will not be a useful way to go about sharing content for an app that's meant to run on any Android device.
In manifest.xml:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.myapp.SharingActivity"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Take careful note of how you specify the authorities. You must specify the activity from which you will create the URI and launch the share intent, in this case the activity is called SharingActivity. This requirement is not obvious from Google's docs!
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="just_a_name" path=""/>
</paths>
Be careful how you specify the path. The above defaults to the root of your private internal storage.
In SharingActivity.java:
Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));
In this example we are sharing a JPEG image.
Finally it is probably a good idea to assure yourself that you have saved the file properly and that you can access it with something like this:
File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
Log.w(TAG, "File not found!");
}
In my app FileProvider works just fine, and I am able to attach internal files stored in files directory to email clients like Gmail,Yahoo etc.
In my manifest as mentioned in the Android documentation I placed:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.package.name.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
And as my files were stored in the root files directory, the filepaths.xml were as follows:
<paths>
<files-path path="." name="name" />
Now in the code:
File file=new File(context.getFilesDir(),"test.txt");
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);
shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
"Test");
shareIntent.setType("text/plain");
shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
new String[] {"email-address you want to send the file to"});
Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
file);
ArrayList<Uri> uris = new ArrayList<Uri>();
uris.add(uri);
shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
uris);
try {
context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
catch(ActivityNotFoundException e) {
Toast.makeText(context,
"Sorry No email Application was found",
Toast.LENGTH_SHORT).show();
}
}
This worked for me.Hope this helps :)
If you get an image from camera none of these solutions work for Android 4.4. In this case it's better to check versions.
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
uri = Uri.fromFile(file);
} else {
uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, CAMERA_REQUEST);
}
grantUriPermission (from Android document)
Normally you should use Intent#FLAG_GRANT_READ_URI_PERMISSION or
Intent#FLAG_GRANT_WRITE_URI_PERMISSION with the Intent being used to
start an activity instead of this function directly. If you use this
function directly, you should be sure to call revokeUriPermission(Uri,
int) when the target should no longer be allowed to access it.
So I test and I see that.
If we use grantUriPermission before we start a new activity, we DON'T need
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION in Intent to overcome SecurityException
If we don't use grantUriPermission. We need to use FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION to overcome SecurityException but
Your intent MUST contain Uri by setData or setDataAndType else SecurityException still throw. (one interesting I see: setData and setType can not work well together so if you need both Uri and type you need setDataAndType. You can check inside Intent code, currently when you setType, it will also set uri= null and when you setUri it will also set type=null)
just to improve answer given above:
if you are getting NullPointerEx:
you can also use getApplicationContext() without context
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
I want to share something that blocked us for a couple of days: the fileprovider code MUST be inserted between the application tags, not after it.
It may be trivial, but it's never specified, and I thought that I could have helped someone!
(thanks again to piolo94)
I want to open an image from internal folder with the android default image viewer, on the Nexus 7 tablet.
I use the following code,but for some reason the image is not displayed. What I'm doing wrong? The path to the file is :
file:///data/data/com.example.denandroidapp/files/Attachments/photoTemp/photo.jpg
(this is what Uri.parse("file://" + file) returns).
ArticlePhoto photo = new ArticlePhoto(soapObject);
File f = new File(context.getFilesDir() + "/Attachments/photoTemp");
if(!f.exists())
f.mkdirs();
if (photo.ArtPhoto != null) {
Bitmap articlePhoto = BitmapFactory.decodeByteArray(photo.ArtPhoto, 0, photo.ArtPhoto.length);
ByteArrayOutputStream bytesFile = new ByteArrayOutputStream();
articlePhoto.compress(Bitmap.CompressFormat.JPEG, 100, bytesFile);
File file = new File(f + "/photo.jpeg");
try {
if(!file.exists())
file.createNewFile();
FileOutputStream outStream = new FileOutputStream(file);
outStream.write(bytesFile.toByteArray());
outStream.close();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + file),"image/jpeg");
startActivity(intent);
} catch(Exception ex) {
AlertDialog alert = new AlertDialog.Builder(context).create();
alert.setTitle("Warning!");
alert.setMessage(ex.getMessage());
alert.show();
}
}
Try with this :
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
Uri uri = Uri.parse("file://" + file.getAbsolutePath());
intent.setDataAndType(uri,"image/*");
startActivity(intent);
Thanks.
The problem is that the image is internal to your application! So an external application (Image Viewer) has no access to the data that is internal to your application.
What you might have to do is create a Content Provider .
http://web.archive.org/web/20111020204554/http://www.marcofaion.it/?p=7
Android Manifest.xml
<provider android:authorities="com.example.denandroidapp" android:enabled="true" android:exported="true" android:name=<fully classified name of provider class>>
</provider>
Creating Intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.parse("content://com.example.denandroidapp/" + filename);
intent.setDataAndType(uri, "image/jpeg");
If a file is associated with your app (stored on the internal storage of your app space ), other apps can not access your file directly provided a valid file path. Instead, you have to create a file provider and generate a content uri.
First, add the file provider in your AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Then you need to create an a file named file_paths in xml/file_paths.xml (the directory xml is not created by default, so create it).
file_paths.xml looks like
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="myFiles" path="./"/>
</paths>
add as much as paths you want your provider to access in .
atlast you need to create your intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
File imagePath = new File(context.getFilesDir(), "fileName");
Uri contentUri = FileProvider.getUriForFile(context, "com.mydomain.fileprovider", imagePath);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(contentUri,"image/*");
context.startActivity(intent);
Note: make sure the file path sepecefied in file_paths.xml and new File(context.getFilesDir(),"fileName"), matches. getFilesDir() will give you the root directory of your app.
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(outputFileName)),"image/jpeg");
startActivity(intent);
You can use FileProvider which extends ContentProvider
Check the link -
https://developer.android.com/reference/android/support/v4/content/FileProvider
To specify the FileProvider component itself, add a "provider" element to your app manifest.
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
You must specify a child element of "paths" for each directory that contains files for which you want content URIs. For example, these XML elements specify two directories
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
<files-path name="my_docs" path="docs/"/>
</paths>
After this you need to generate content URI for the file and then call the intent, refer the below link
https://developer.android.com/reference/android/support/v4/content/FileProvider#GetUri
Check this: https://stackoverflow.com/a/11088980/1038442;
File file = new File(filePath);
MimeTypeMap map = MimeTypeMap.getSingleton();
String ext = MimeTypeMap.getFileExtensionFromUrl(file.getName());
String type = map.getMimeTypeFromExtension(ext);
if (type == null)
type = "*/*";
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.fromFile(file);
intent.setDataAndType(data, type);
startActivity(intent);
PS: if your are trying to open .jpg files, try to
Replace String ext = MimeTypeMap.getFileExtensionFromUrl(file.getName());
With String ext = MimeTypeMap.getFileExtensionFromUrl(".jpg");
Good luck.
In my case, The gallery launched but did not show any images and went straight to the home page. My situation is quite different from what OP faced but I think it's worth mentioning here since the question is about images not being shown through implicit intent.
My problem came from the code below.
val intent = context.packageManager.getLaunchIntentForPackage(packageName ?: "")
The code above tells PackageManager to launch the entry point of the application, instead of the activity that shows the images.
If you look at the Logcat above, you can find that the intent launched with cat=[android.intent.category.Launcher] will go to SplashActivity. This happened because I created the intent with getLaunchIntentForPackage()
Alternative is to use Intent with setPackage() like the code in below
val intent = Intent()
val uri = Uri.fromFile(file) // You should probably replace with ContentProvider's uri
intent.apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
action = Intent.ACTION_VIEW
setPackage(packageName)
setDataAndType(uri, "image/*")
}
context.startActivity(intent)