My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
My case : I am running a job combining dynamics crm that uses aspose to create pdf files, everything was working fine suddenly I got the error - The invoked member is not supported in a dynamic assembly. At the current moment In my dev environment everything works fine only In my qa environment I get that exception. I checked the path I use it is correct. I tried to put the licence near the dll file in a specific file etc'... still I get this msg, what is the solution in my case ?
my function:
public void EnsureAsposeLicenseIsSet()
{
if (!AsposeLicenseWasSet)
{
lock ("EnsureAsposeLicenseIsSet")
{
if (!AsposeLicenseWasSet)
{
License wordLicense = new License();
wordLicense.SetLicense(GetConfigByKey("Asposelicence"));
AsposeLicenseWasSet = true;
}
}
}
}
Include the Aspose license file as an Embedded resource instead, and it will work.
My goal is to have appsettings.json file with production configurations and have possibility to costomize it for every developer, e.g. use local connection strings. So it does not similar to transform web.config mechanism, i don't want depends on bulid configuration. Can anyone provide solution for this goal?
In one of my past project we do so: we store all configure information in custom config.xml and parsed it into the custom structure. Web.config contains only server configaration. every developer has own copy of config files with his own data. Solution is that application use configuration files from path, that specified in environment path in windows via Environment.GetEnvironmentVariable("key").
Does anyone have idea better than my one?
This is how I manage configuration: see comments in the code
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json"); // this one has default configuration
// this file name is added to my gitignore so it won't get committed,
// I keep local dev configuration there
builder.AddJsonFile("appsettings.local.overrides.json", optional: true);
if (env.IsDevelopment())
{
// This reads the configuration keys from the secret store.
// if you need a more secure place for dev configuration use usersecrets
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
// the order in which config sources is added is important, a source added later
// will override the same settings from a source added before
// environment variables is usually for production and therefore added last to give it higher priority
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
I'm writing an application that will run on the Okuma control and have application settings.
Since one of the conditions is that application's settings must be easily backed up, I'm keeping them in the application directory. It works on the control because applications go to the D: but if someone installs the application on a PC on the C drive, the application can't access it's own application directory and it gets errors.
Conditions:
Windows 7
P300 control
Application being installed to D-drive
Has to work if someone installs to the C-drive on a PC
Is there a standard spot to put all application settings?
Continue to keep your application settings and other data in the application's install directory. There is no need to change the directory locations just for a "PC only" install.
The solution to file access issues is to change file permissions during install.
For example, this answer someone posted using WIX installer.
A similar question is answered here.
You could use code similar to this to change permissions during install (when the user has admin privileges)
using System.Security.Principal;
public static void SetPermissions()
{
String path = GetPath();
try
{
// Create security idenifier for all users (WorldSid)
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
DirectoryInfo di = new DirectoryInfo(path);
DirectorySecurity ds = di.GetAccessControl();
// add a new file access rule w/ write/modify for all users to the directory security object
ds.AddAccessRule(new FileSystemAccessRule(sid,
FileSystemRights.Write | FileSystemRights.Modify,
InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, // all sub-dirs to inherit
PropagationFlags.None,
AccessControlType.Allow)); // Turn write and modify on
// Apply the directory security to the directory
di.SetAccessControl(ds);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
My scenario is this:
I have a custom RavenDB membership provider that is implemented in a class library (DLL). This provider needs to access a database to store and retrieve User and Role information. I'd like to use the same app database to store membership information to avoid having one more database.
I don't know how to get a reference to the already initialized database (app database) inside the class library code. I think I'm going the wrong way here... :)
Some code:
bool embeddedStore = Convert.ToBoolean(config["enableEmbeddableDocumentStore"]);
if (embeddedStore)
{
_documentStore = new EmbeddableDocumentStore()
{
// Here I'm using the same connection string used by the app.
// This gives me an error when I try to open a session in the DocumentStore.
ConnectionStringName =
config["connectionStringName"]
};
}
else
{
_documentStore = new DocumentStore()
{
ConnectionStringName =
config["connectionStringName"]
};
}
This is the connection string present in Web.config:
<add name="RavenDB" connectionString="DataDir = ~\App_Data\Database" />
How can I reuse the same database within the custom membership provider? Any ideas?
I thought about moving the class library code files to the Web project. This way I could get a reference to the DocumentStore easily, but the code wouldn't be as organized as I'd like.
I also tried to use 2 RavenDB databases: 1 for the app and 1 for the membership provider, but as I'm running RavenDB in its embeddable fashion I couldn't get it working.
These are the errors I got during my attempts so far:
RavenDB Could not open transactional storage.
Temp path already used by another database instance.
You need to pass the instance of the opened document store to your dll.
You can do that using a container or by providing an API call to do that.
You can't have two instance using the same db.