Gmail 5.0应用在收到ACTION_SEND意图时失败并显示“附件权限被拒绝”

时间:2022-11-15 12:13:25

My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.

我的应用程序使用附件创建邮件,并使用Intent.ACTION_SEND的意图来启动邮件应用程序。

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".

它适用于我测试过的所有邮件应用,除了新的Gmail 5.0(适用于Gmail 4.9),邮件打开时没有附件,显示错误:“附件权限被拒绝”。

There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.

在logcat上没有来自Gmail的有用消息。我只测试了Android KitKat上的Gmail 5.0,但是在多个设备上。

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.

我知道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!

谢谢!

8 个解决方案

#1


23  

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.

GMail 5.0为从Intent接收的附件添加了一些安全检查。这些与unix权限无关,因此文件可读的事实并不重要。

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.

当附件Uri是file://时,它只接受来自外部存储的文件,gmail本身的私有目录或来自调用应用程序的私有数据目录的世界可读文件。

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.

这种安全检查的问题在于它依赖于gmail能够找到调用者应用程序,这只有在调用者请求结果时才可靠。在上面的代码中,您不会要求结果,因此gmail不知道调用者是谁,并拒绝您的文件。

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.

因为它适用于4.9但不适用于5.0,你知道它不是unix权限问题,所以原因必须是新的检查。

TL;DR answer: replace startActivity with startActivityForResult.

TL; DR回答:用startActivityForResult替换startActivity。

Or better yet, use a content provider.

或者更好的是,使用内容提供商。

#2


28  

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.

我能够通过Intent将截屏.jpeg文件从我的应用程序传递到GMail 5.0。关键在于这个答案。

Everything I have from @natasky 's code is nearly identical but instead, I have the file's directory as

我从@natasky的代码中得到的一切几乎相同,但我把文件的目录作为

context.getExternalCacheDir();

Which "represents the external storage directory where you should save cache files" (documentation)

哪个“代表应保存缓存文件的外部存储目录”(文档)

#3


12  

Use getExternalCacheDir() with File.createTempFile.

Use the following to create a temporary file in the external cache directory:

使用以下命令在外部缓存目录中创建临时文件:

String fileName      = "my_temp_file";
String fileExtension = ".tmp";

File temporaryFile = File.createTempFile( fileName, fileExtension, context.getExternalCacheDir() );

#4


5  

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,它可以为您应用的内部文件创建Uris。其他应用程序被授予阅读这些Uris的权限。然后,只需调用Uri.fromFile(attachment),就可以实例化FileProvider并使用:

fileProvider.getUriForFile(attachment);

#5


3  

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.

我测试了它,我发现它肯定是私有存储访问问题。将某些文件附加到Gmail(超过5.0)时,请不要使用私有存储中的文件,例如/ data / data / package /。尝试使用/ storage / sdcard。

You can successfully attach your file.

您可以成功附加文件。

#6


2  

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

不知道为什么GMail 5.0不喜欢某些文件路径(我已经确认它确实具有读访问权限),但一个明显更好的解决方案是实现自己的ContentProvider类来提供文件。它实际上有点简单,我在这里找到了一个不错的例子: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.

请务必将标记添加到您的应用清单中,并在其中包含“android:grantUriPermissions =”true“”。您还需要实现getType()并为文件URI返回相应的MIME类型,否则某些应用程序将无法使用此...在链接的注释部分中有一个示例。

#7


2  

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.

    将数据存储在您自己的ContentProvider中,确保其他应用具有访问您的提供商的正确权限。提供访问的首选机制是使用per-URI权限,这是临时的,只授予对接收应用程序的访问权限。创建这样的ContentProvider的简单方法是使用FileProvider助手类。

  • 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.

    使用系统MediaStore。 MediaStore主要针对视频,音频和图像MIME类型,但从Android 3.0(API级别11)开始,它还可以存储非媒体类型(有关详细信息,请参阅MediaStore.Files)。可以使用scanFile()将文件插入MediaStore,然后将适合共享的content:// style Uri传递给提供的onScanCompleted()回调。请注意,一旦添加到系统MediaStore,设备上的任何应用程序都可以访问该内容。

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.

最后,您可以将文件复制/存储在外部存储中 - 那里不需要权限。

#8


1  

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

在此代码中,“myPic”是由相机意图返回的位图

#1


23  

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.

GMail 5.0为从Intent接收的附件添加了一些安全检查。这些与unix权限无关,因此文件可读的事实并不重要。

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.

当附件Uri是file://时,它只接受来自外部存储的文件,gmail本身的私有目录或来自调用应用程序的私有数据目录的世界可读文件。

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.

这种安全检查的问题在于它依赖于gmail能够找到调用者应用程序,这只有在调用者请求结果时才可靠。在上面的代码中,您不会要求结果,因此gmail不知道调用者是谁,并拒绝您的文件。

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.

因为它适用于4.9但不适用于5.0,你知道它不是unix权限问题,所以原因必须是新的检查。

TL;DR answer: replace startActivity with startActivityForResult.

TL; DR回答:用startActivityForResult替换startActivity。

Or better yet, use a content provider.

或者更好的是,使用内容提供商。

#2


28  

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.

我能够通过Intent将截屏.jpeg文件从我的应用程序传递到GMail 5.0。关键在于这个答案。

Everything I have from @natasky 's code is nearly identical but instead, I have the file's directory as

我从@natasky的代码中得到的一切几乎相同,但我把文件的目录作为

context.getExternalCacheDir();

Which "represents the external storage directory where you should save cache files" (documentation)

哪个“代表应保存缓存文件的外部存储目录”(文档)

#3


12  

Use getExternalCacheDir() with File.createTempFile.

Use the following to create a temporary file in the external cache directory:

使用以下命令在外部缓存目录中创建临时文件:

String fileName      = "my_temp_file";
String fileExtension = ".tmp";

File temporaryFile = File.createTempFile( fileName, fileExtension, context.getExternalCacheDir() );

#4


5  

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,它可以为您应用的内部文件创建Uris。其他应用程序被授予阅读这些Uris的权限。然后,只需调用Uri.fromFile(attachment),就可以实例化FileProvider并使用:

fileProvider.getUriForFile(attachment);

#5


3  

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.

我测试了它,我发现它肯定是私有存储访问问题。将某些文件附加到Gmail(超过5.0)时,请不要使用私有存储中的文件,例如/ data / data / package /。尝试使用/ storage / sdcard。

You can successfully attach your file.

您可以成功附加文件。

#6


2  

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

不知道为什么GMail 5.0不喜欢某些文件路径(我已经确认它确实具有读访问权限),但一个明显更好的解决方案是实现自己的ContentProvider类来提供文件。它实际上有点简单,我在这里找到了一个不错的例子: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.

请务必将标记添加到您的应用清单中,并在其中包含“android:grantUriPermissions =”true“”。您还需要实现getType()并为文件URI返回相应的MIME类型,否则某些应用程序将无法使用此...在链接的注释部分中有一个示例。

#7


2  

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.

    将数据存储在您自己的ContentProvider中,确保其他应用具有访问您的提供商的正确权限。提供访问的首选机制是使用per-URI权限,这是临时的,只授予对接收应用程序的访问权限。创建这样的ContentProvider的简单方法是使用FileProvider助手类。

  • 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.

    使用系统MediaStore。 MediaStore主要针对视频,音频和图像MIME类型,但从Android 3.0(API级别11)开始,它还可以存储非媒体类型(有关详细信息,请参阅MediaStore.Files)。可以使用scanFile()将文件插入MediaStore,然后将适合共享的content:// style Uri传递给提供的onScanCompleted()回调。请注意,一旦添加到系统MediaStore,设备上的任何应用程序都可以访问该内容。

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.

最后,您可以将文件复制/存储在外部存储中 - 那里不需要权限。

#8


1  

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

在此代码中,“myPic”是由相机意图返回的位图