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

Posted

技术标签:

【中文标题】Gmail 5.0 应用程序在收到 ACTION_SEND 意图时失败并显示“附件的权限被拒绝”【英文标题】:Gmail 5.0 app fails with "Permission denied for the attachment" when it receives ACTION_SEND intent 【发布时间】:2015-01-09 02:03:49 【问题描述】:

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

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

logcat 上没有来自 Gmail 的有用邮件。我只在 android KitKat 上测试了 Gmail 5.0,但在多个设备上测试。

我像这样为附件创建文件:

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);

我知道MODE_WORLD_READABLE 的安全问题。

我这样发送意图:

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);

在创建文件或发送意图时我做错了什么吗?有没有更好的方法来启动带有附件的邮件应用程序?或者 - 有人遇到过这个问题并找到了解决方法吗?

谢谢!

【问题讨论】:

@nobalG 与此问题无关。 我也遇到了这个问题,附件的位置似乎与问题有关,因为从 sd 卡共享附件仍然有效。 (由于 WORLD_READABLE 标志,这应该无关紧要)这可能是一个错误吗? 同样的问题...我仍然可以使用 ACTION_SEND 将文件发送到 Google Drive,但是当我选择 GMail(使用新版本)时,它会像您看到的那样失败。在以前版本的 GMail 上使用相同的代码没有问题。 在共享本地 PDF 文件时遇到了同样的问题。找到了这个解决方案,使用一个最小的 github 示例项目进行基本的 FileProvider 设置:***.com/a/48103567/2162226,其中添加和发送附件是那里的代码的一部分。它提供了将哪些文件复制(不进行更改)到本地独立项目的步骤 【参考方案1】:

我能够通过 Intent 将屏幕截图 .jpeg 文件从我的应用程序传递到 GMail 5.0。密钥在this answer。

我从 @natasky 的代码中获得的所有内容几乎相同,但相反,我将文件目录设置为

context.getExternalCacheDir();

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

【讨论】:

这解决了我的问题。更改为 startActivityForResult 后,我​​在 Gmail 中收到“无法附加空文件”的错误,即使我可以验证文件不为空。问题是我在“getCacheDir()”目录中创建文件。更改为“getExternalCacheDir()”导致附件成功! 非常感谢,显然这是让它在我的应用程序中运行的关键。 这是为我做的。我实际上使用的是File.createTempFile,它接受位置的第三个参数。我为第三个参数传递了activity.getExternalCacheDir(),一切都很顺利。谢谢!【参考方案2】:

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

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

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

由于它在 4.9 中对您有效,但在 5.0 中无效,因此您知道这不是 unix 权限问题,因此原因一定是新的检查。

TL;DR 答案: 将 startActivity 替换为 startActivityForResult。

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

【讨论】:

谢谢,使用 startActivityForResult 允许 GMail 创建邮件但无法发送:libcore.io.ErrnoException: open failed: EACCES (Permission denied) at libcore.io.Posix.open (本机方法)。我正在通过 final File tempImage = File.createTempFile("image", ".jpg"); 创建附件文件 当我尝试这个时,我仍然得到一个权限被拒绝的吐司。也许内容提供商是唯一的出路 在这种情况下,它确实是一个文件权限问题。 createTempFile 创建一个只有创建它的 uid 才能读取的文件。不是本页中描述的问题。此页面中描述的问题是该文件是世界可读的,但 gmail 由于其内部检查而拒绝读取它,这就是 startActivityForResult 解决的问题。但如果 gmail 无法读取您的文件,则需要单独处理。 我有一个不再可读的世界可读文件,但是用 startActivityForResult 替换 startActivity 为我解决了这个问题。谢谢! 请提供一些代码sn-p以便我们使用【参考方案3】:

getExternalCacheDir()File.createTempFile 一起使用。

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

File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());

然后将你原来的文件内容复制到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();

现在将您的文件置于意图中,

emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));

【讨论】:

Josh 是否愿意详细说明您对此的使用情况?我将我的文件复制到一个临时文件中 @wal 我以两种方式使用它,一种是将文件复制到带有FileUtils.copyFile(file, temporaryFile); 的新创建的临时文件中。另一种方法是创建临时文件并使用缓冲区用数据填充该文件。这有帮助吗? 这只是一种解决方法,不必要地复制数据,占用双倍空间,必须记住清除缓存目录。最好使用 FileProvider。【参考方案4】:

您应该implement a FileProvider,它可以为您应用的内部文件创建 Uris。其他应用程序被授予读取这些 Uris 的权限。然后,您无需调用 Uri.fromFile(attachment),而是实例化您的 FileProvider 并使用:

fileProvider.getUriForFile(attachment);

【讨论】:

【参考方案5】:

Google 为该问题提供了 answer:

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

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

您也可以尝试set permissions 为您的文件:

emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

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

【讨论】:

【参考方案6】:

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

您可以成功附加文件。

【讨论】:

【参考方案7】:

不确定为什么 GMail 5.0 不喜欢某些文件路径(我已经确认它确实具有读取权限),但显然更好的解决方案是实现您自己的 ContentProvider 类来提供文件。它实际上有点简单,我在这里找到了一个不错的例子:http://stephendnicholas.com/archives/974

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

【讨论】:

【参考方案8】:

我遇到了这个问题,终于找到了一种简单的方法来发送带有附件的电子邮件。这是代码

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();
    

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

【讨论】:

感谢您的代码。它对我来说很好,但有 Josh Pinter 的注释(见下一个答案)。【参考方案9】:

第 1 步:在附加的 URI 中添加权限

Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);

与您的清单文件提供名称相同

android:authorities="com.yourpackage"

第 2 步`;添加允许读取的标志

myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

【讨论】:

以上是关于Gmail 5.0 应用程序在收到 ACTION_SEND 意图时失败并显示“附件的权限被拒绝”的主要内容,如果未能解决你的问题,请参考以下文章

Android ACTION_SEND 超链接在 GMail 中不起作用

Android - 对收到的推送通知进行分组,如 Gmail

收到来自 gmail users.watch API 的推送通知后如何阅读用户 gmail?

为啥我在通过 IMAP 连接到 Gmail 时收到“需要 Web 登录”消息?

Action Mailer NameError:未定义的局部变量或方法“smtp”

为啥无法收到电子邮件(python、gmail、django)?