I have been trying to share a pdf file, I set up the FileProvider like this:
On the main manifest xml:
<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/file_paths" />
</provider>
The res/xml/file_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_files" path="." />
</paths>
And in my code I am trying the following:
String path = root.getAbsolutePath() + "/file.pdf";
final Uri data = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID+".fileprovider", new File(path));
getApplicationContext().grantUriPermission(getApplicationContext().getPackageName(), data, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(data, "application/pdf").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
getApplicationContext().startActivity(intent);
Returns error:
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/file.pdf
The documentation for FileProvider says that <files-path>:
Represents files in the files/ subdirectory of your app's internal storage area. This subdirectory is the same as the value returned by Context.getFilesDir().
Your file is not in internal storage. It is in external storage. For that, you need an <external-path> element, not a <files-path> element.
The accepted answer is correct, but he didn't specify the API to use, so here it is.
val imagePath = File(
Environment.getExternalStorageDirectory().path +
File.separator, "Your folder name"
)
val newfile = File(imagePath, "Your file name")
val imageUri = FileProvider.getUriForFile(
context,
"com.example.domain.provider",
newfile
)
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "image/jpg" // set file type
shareIntent.putExtra(
Intent.EXTRA_STREAM,
imageUri
)
context.startActivity(Intent.createChooser(shareIntent, "Share Status Saver Image"))
Happy Coding!
Related
I am creating a .xls file and saving it to the Downloads folder. I am trying to send the .xls file as an email attachment. However, when Gmail opens up I get a toast error message that says Couldn't Attach File. This error message is coming from Gmail. I have verified that the file is being created properly. I am not sure why it's not attached to the email.
Here is the code that creates the file and emailIntent.
try{
String nowDateTime = System.currentTimeMillis()+"";
File filePath = new File(Environment.getExternalStorageDirectory()+"/Download/"+"ExcelFile_"+nowDateTime+".xls");
if(!filePath.exists()){
filePath.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
hssfWorkbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
Intent emailIntent = new Intent(Intent.ACTION_SEND);
Uri uri = Uri.fromFile(filePath);
Log.e("FilePath", filePath.toString());
Log.e("Uri", uri.toString());
emailIntent.setType("application/excel");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.Email_Subject_Line));
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(emailIntent);
}catch(Exception e){
Log.e("GraphReportDialog", "Creating Excel Sheet File Output Stream Error "+ e.getMessage());
}
Manifest Permissions:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
CommonsWare was correct about using FileProvider. The documentation says Uri.fromFile() is less secure than FileProvider.
Once I added 3 main things, it worked as expected.
The first thing is adding providers in the manifest file:
<provider
android:authorities="com.example.counter"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"
/>
</provider>
The second thing is adding the paths. I created another directory in my res file called xml. I then added an xml file called provider_paths.xml. Here is that code:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path <!-- there are several different types of paths to use see the documentation-->
name="external_files"
path="." /> <!-- consider changing this to more specific path-->
</paths>
Lastly, this is how I implemented it.
String nowDateTime = System.currentTimeMillis()+"";
File filePath = new File(Environment.getExternalStorageDirectory()+"/Download/"+"ExcelFile_"+nowDateTime+".xls");
if(!filePath.exists()){
filePath.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
hssfWorkbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
Intent emailIntent = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(this.getContext(),"com.example.counter", filePath);
emailIntent.setType("application/excel");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.Email_Subject_Line));
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(emailIntent);
Hey so I save my pdf in external data storage. Eg:
Environment.getExternalStorageDirectory().getPath() + "/file.pdf"
Then, I try to attach it to intent to send:
File attachment = this.getFileStreamPath(fileDirectory + "/" + fileName);
Uri uri = Uri.fromFile(attachment);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setDataAndType(Uri.parse("mailto:"), "text/plain"); // I have also tried "application/pdf"
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Calc PDF Report");
emailIntent.putExtra(Intent.EXTRA_TEXT, " PDF Report");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
finish();
and im getting the error:
Caused by: java.lang.IllegalArgumentException: File /storage/emulated/0/file.pdf contains a path separator
I am thinking it is something wrong with were I am saving my file, but can't find any examples that are up-to-date.
To share a file as an email attachment using intent, you need to use a FileProvider.
/**
* Generate file content and returns uri file
*/
public static Uri generateFile(Context context) {
File pdfDirPath = new File(context.getFilesDir(), "pdfs");
pdfDirPath.mkdirs();
File file = new File(pdfDirPath, "attachment.pdf");
file.deleteOnExit();
Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".file.provider", file);
FileOutputStream os = null;
try {
Logger.info("Generate file " + file.getAbsolutePath());
os = new FileOutputStream(file);
document.writeTo(os);
document.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
return uri;
}
private void share(Context context) {
Uri uri = generateFile(context);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(EXTRA_SUBJECT, "Send something");
emailIntent.putExtra(Intent.EXTRA_TEXT, "You receive attachment");
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(emailIntent);
}
In your app add the file provider definition:
AndroidManifest.xml
<application
android:name=".DemaApplication"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.file.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
...
</application>
provider_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="internal_files"
path="/"/>
<!--<external-path name="external_files" path="./files"/>-->
</paths>
At last but not least, you need to specify the file provider path (where are your files). I hope this helps. Here, the official documentation about how to send email and attachment with intent.
Problem #1: getFileStreamPath() does not support subdirectories and is not related to external storage, where your file resides.
Problem #2: Uri.fromFile() will not work on Android 7.0, as your app will crash with a FileUriExposedException. To fix this and Problem #1, use FileProvider to set up a content Uri that you can use for EXTRA_STREAM.
Problem #3: ACTION_SEND does not use a "data" Uri (i.e., your "mailto:" should not be there).
Problem #4: The MIME type of a PDF is not text/plain — as your comment notes, use application/pdf.
Problem #5: getExternalStorageDirectory() is deprecated on Android 10 and higher, and you will not be able to write files there. Consider using getExternaFilesDir(null) (called on a Context) for a better location that works without permissions and on more Android OS versions.
can't find any examples that are up-to-date
The documentation covers the use of ACTION_SEND.
i trying to share a mp3 audio from the app, the problem is, the audio sends without format, i read about saving it to the external memory and then share it, but i dont have a external memory, there is a way like saving it to internal memory or something like that? thanks. here is the code:
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("audio/mpeg3");
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Los simuladores");
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("android.resource://arg.com.cevascoit.botoneralossimuladores/" + R.raw.teagachas));
startActivity(Intent.createChooser(sendIntent, getString(R.string.app_name)));
return false;
Use a FileProvider like in this example:
First create a file in internal storage (eg.your mp3 file)
File sharedDir = new File(getFilesDir(), "shared");
if (!sharedDir.exists())
sharedDir.mkdir();
File fileToShare = new File(sharedDir, "hello_world.txt");
Writer writer = new OutputStreamWriter(new FileOutputStream(fileToShare));
writer.write("Hello World");
writer.close();
Then define the FileProvider in the manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="ese.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />;
</provider>;
Then specify the directories to be shared in a filepaths.xml
<files-path
name="shared files"
path="shared/" />
Create an URI for the file
Uri uri = FileProvider.getUriForFile(this, "ese.fileprovider", fileToShare);
Share the file
Intent shareFile = new Intent(Intent.ACTION_SEND);
shareFile.setType("application/pdf");
shareFile.putExtra(Intent.EXTRA_STREAM, uri);
shareFile.putExtra(Intent.EXTRA_SUBJECT, "Sharing " + fileToShare.getName());
shareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File");
startActivity(Intent.createChooser(shareFile, "Share File"));
I am trying to get the URI for a file like this:
Uri photoURI = FileProvider.getUriForFile(this,
"br.com.[blablabla].sgaconsultor",
createImageFile());
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "registroFoto";
ContextWrapper cw = new ContextWrapper(getApplicationContext());
File directory = cw.getDir("imageDir", Context.MODE_PRIVATE);
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
directory
);
}
I have a file provider:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="br.com.[blablabla].blabla"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And the xml is:
<paths>
<files-path name="br.com.[blablabla].blabla" path="app_imageDir" />
</paths>
But when I run this code I always get this error:
java.lang.IllegalArgumentException: Android: FileProvider IllegalArgumentException Failed to find configured root that contains /data/data/**/files/Videos/final.mp4
But no luck so far... So how can I fix this and get the URI for my file?
Any help is really appreciated!
So how can I fix this and get the URI for my file?
Step #1: Get rid of the useless stuff in the Java code.
Step #2: Get the Java code and the file_paths resource to match, as file_paths says that the file path has to be under getFilesDir() and have app_imageDir in it, and your Java code does not have either of those things.
So, try this:
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "registroFoto";
File directory = new File(getFilesDir(), "app_imageDir");
directory.mkdirs();
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
directory
);
}
I'm trying to pass an image that resides in the res/raw directory of my app along with a share intent.
I followed the process described in the FileProvider docs, and here's my code:
AndroidManifest.xml
<application ...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
</application>
res/xml/paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="shared" path="./"/>
</paths>
The code in my activity:
String shareToPackage = ...
File imageFile = new File(context.getFilesDir().getPath() + "/image");
if (!imageFile.exists()) { // image isn't in the files dir, copy from the res/raw
final InputStream inputStream = context.getResources().openRawResource(R.raw.my_image);
final FileOutputStream outputStream = context.openFileOutput("image", Context.MODE_PRIVATE);
byte buf[] = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
imageFile = new File(context.getFilesDir().getPath() + "/image");
}
if (!imageFile.exists()) {
throw new IOException("couldn't find file");
}
final Uri uri = Uri.fromFile(imageFile);
context.grantUriPermission(shareToPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TEXT, "here's the image");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setPackage(shareToPackage);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
The above does not work as the file that I get in the other app isn't accessible:
java.io.FileNotFoundException: FILE_PATH: open failed: EACCES
(Permission denied)
Any idea what I'm doing wrong here?
Thanks.
Get rid of the path attribute in <files-path>, as it is not needed here, since you are serving everything from getFilesDir().
Do not use string concatenation when creating File objects. Replace:
new File(context.getFilesDir().getPath() + "/image.png");
with:
new File(context.getFilesDir().getPath(), "image.png");
Most importantly, do not use Uri.fromFile(). Use FileProvider.getUriForFile(). As it stands, you are going through all this work to set up FileProvider, then you do not use the FileProvider for making the content available to the other app.
Or, get rid of all of this, and use my StreamProvider, which can serve a raw resource directly.
Or, write your own ContentProvider that serves the raw resource directly.
#nitzan-tomer, see https://stackoverflow.com/a/33031091/966789
What Are Runtime Permissions?
With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.