There are many posts on this topic, but I can't find the solution for my problem...
Following: I would like to send a file out of my app via an e-mail attachment.
Sending the file via Whatsapp, save to Google Drive,... works, but not for K-9 Mail or Gmail ("Unable to attache file" Toast message is displayed).
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
intentShareFile.setType("application/zip");
intentShareFile.putExtra(Intent.EXTRA_STREAM, Uri.parse("/sdcard/Download/ExportFile.zip"));
//intentShareFile.putExtra(Intent.EXTRA_TEXT, "message");
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intentShareFile.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
I don't understand why it works for all apps, except e-mail apps.
Can anyone help me out?Thanks in advance.
I think your issue is related to the path of the file you are using here (i.e Uri.parse("/sdcard/Download/ExportFile.zip"))
There is a code example to send an email with an attachment on the Android Developers docs here. In the example they pass the Uri as follows:
Uri.parse("content://path/to/email/attachment"). Notice this is called a ContentUri, read more about it form here.
Hope this turns useful for you!
Amr EIZawawy is right. I need to create the Uri of the file and use the File Provider API.
I don't know if I did too much, but this is my solution:
Create a file called "file_paths.xml" in a directory "xml" (create first) within your "res" directory (sibling of layout directory).
The file needs to contain the path to the file you want to share. For the external Download directory this is:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="download" path="Download/"/>
</paths>
// The internal path you'd be <files-path name.../> see [FileProvider][1] for all possibilities
Define the File provider within the AndroidManifest.xml with a meta-data link to the just created XML file:
<application
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.<your-app-package-name>.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" /> // Link to the above created file
</provider>
...
Implement the code:
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
intentShareFile.setType("application/zip");
File fileDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File newFile = new File(fileDirectory, "ExportFile.zip");
String authority = "com.bennoss.myergometertrainer.fileprovider"; // Must be the same as in AndroidManifest.
Uri contentUri = FileProvider.getUriForFile(getContext(), authority, newFile);
intentShareFile.putExtra(Intent.EXTRA_STREAM, contentUri);
//intentShareFile.putExtra(Intent.EXTRA_TEXT, "xxx");
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
For more information see: FileProvider
Related
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.
An Android app can create PDF files which should be stored to the external storage and viewed or shared by the user.
Creating, storing and sharing the file is no problem. However viewing the file fails and only a blank screen is shown.
This is my code:
<!-- Provider definition in manifest -->
<provider
android:authorities="com.example.MyApp.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
<!-- file_path.xml in xml resource dir -->
<?xml version="1.0" encoding="utf-8"?>
<path xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="ExternalDir" path="Documents/MyApp" />
</path>
// Sharing and viewing
File baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File appDir = new File(baseDir, "MyApp");
appDir.mkdirs();
File pdfFile = new File(appDir, "Test.pdf");
savePDFToFile(pdfFile)
Uri fileUri = FileProvider.getUriForFile(this, "com.example.MyApp.fileprovider", pdfFile);
// View
Intent viewIntent = new Intent(Intent.ACTION_VIEW);
viewIntent .setDataAndType(fileUri, "application/pdf");
viewIntent .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
viewIntent .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(viewIntent , "View"));
// Sharing
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("application/pdf");
sharingIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
startActivity(Intent.createChooser(sharingIntent, "Share"));
Creating/Writing the PDF file is no problem. Thus there seems to be no problem with the permissions. Sharing the file (e.g. saving it to Google Drive or sending it via email) is also no problem. Thus the FileProvider config and Uri creation seems to be correct.
So, what is wrong with the viewing intent? When testing this in the Emulator using API 26 only a blank page is shown. A user running the same code on a device with API 26 gets a message, that file access is not possible (without a reason).
However the file exists and access permission shouldn't be problem because otherwise the sharing intent would not work as well, would it?
How to solve this?
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.
I create file on the internal storage as suggested by android docs. To be accurate the file is created under a specific directory in the internal storage. I do this using the mode world_readable mode. Then later on i try to attach the file using email program. I was able to get the file attached, however sending the email failed (does not seem to be to load the file) i am sure it is internal storage/permission thingy.
Anyone knows how to fix it or a working example? It will suck to have convert everything on external storage.
Thank you
Ps:I checked other threads and they don't seem to have solutions (old threads)
It is possible to share a file from your apps local storage to another application (such as email attachment) by granting temporary permissions to read that file as part of the share intent.
Step 1: Add a file provider to your AndroidManifest.xml:
<applicaton>
....
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.your.package.name.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
</application>
Step 2: Add a file res/xml/filepaths.xml with the path to the file in local app storage that you want to share:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="myFolder" path="Folder/"/>
</paths>
Step 3: In your java code create the file sharing intent:
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);
shareIntent.setType("text/plain");
shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Your subject");
shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Your message");
ArrayList<Uri> uris = new ArrayList<Uri>();
String shareName = new String(pathToFile + filename);
File shareFile = new File(shareName);
Uri contentUri = FileProvider.getUriForFile(context, "com.your.package.name.fileprovider", shareFile);
uris.add(contentUri);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
// Grant temporary read permission to the content URI
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String msgStr = "Share...";
startActivity(Intent.createChooser(shareIntent, msgStr));
If you have any problems with it see the docs here
https://developer.android.com/training/secure-file-sharing/share-file.html for further details.
I'm assuming you are trying to send the file as an email attachment
using intents.
The reason why the file is empty is that the email app does not have
access to the file in /data/data/package_name/myfile_name, due to
Androids security model (the /data/data/package_name directory is
private to your app).
In order to add the file as an attachment, you need to write it to
public storage (such as the SD card) so the email app can access it.
My app writes data to text files (on sd card and internal memory).
Later the app emails the text files to a list of people.
I am having trouble getting gmail to attach a file that is pulled from the internal application files area. 'Native Android mail' can attach a file from either internal or SD card area with no problem. Gmail will attach a file if it's from SD card, but won't attach a file if its located in internal storage.
// this sends a file from SD - works for android mail and gmail
Intent jj=new Intent(android.content.Intent.ACTION_SEND);
String fileName = "file://" + Environment.getExternalStorageDirectory()+"/aFolder/externalfile.txt"
jj.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileName));
jj.setType("text/plain");
Intent chooser = Intent.createChooser(jj , "Select Sender");
startActivity(chooser);
// this sends an internal file-works for android mail, but no attachment sent with gmail
Intent jj=new Intent(android.content.Intent.ACTION_SEND);
String fileName = "file://" + getFilesDir().toString() + "/internalfile.txt";
jj.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileName));
jj.setType("text/plain");
Intent chooser = Intent.createChooser(jj , "Select Sender");
startActivity(chooser);
Any suggestions?
Do I need to give Gmail special permission somehow?
My attachments are all text files - written by the app.
Internal files were created with openFileOutput(myFile,32769)
Thanks
John D
the only way I found around this was to make my own content provider and pass in the uri to my content provider as the attachment.
Like Dhego, I used a content provider. Specifically, a FileProvider.
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
Using this only requires that you modify your Manifest and create an additional resource file. Also, you will need to obtain a validly formatted URI via a static method provided by FileProvider.
In your Manfiest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.authority.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
The "com.example.authority.fileprovider" is your application authority with "fileprovider" appended.
In the res/xml folder, create a file_paths.xml file that contains the paths to the files you want to expose. In my case, I was exposing them from the application cache directory, so my XML looks like:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="reports" path="reports/"/>
</paths>
In my case, "reports" is a folder within the application cache directory that I am writing files to.
The last thing to do is in your code:
Write files you want to expose to the folders and storage areas specified in file_paths.xml.
Generate a valid URI to set on the Intent you will invoke for sending an email (Intent.ACTION_SEND).
Here's some sample code:
emailIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(getActivity(), "com.example.authority.fileprovider", fileToAttach));
Invoke startActivity on your Intent and that should be it!
It appears that there is indeed an issue with gmail. Unfortunately however, at the time of the writing it seems it hasn't been fixed.