Приложение Gmail 5.0 не работает с "Разрешением, запрещенным для вложения", когда оно получает намерение ACTION_SEND
Мое приложение создает письма с вложениями и использует намерение с Intent.ACTION_SEND
для запуска почтового приложения.
Он работает со всеми почтовыми приложениями, которые я тестировал, за исключением нового Gmail 5.0 (он работает с Gmail 4.9), где почта открывается без вложения, показывая ошибку: "Permission denied for the attachment".
Нет никаких полезных сообщений от Gmail о logcat. Я тестировал только Gmail 5.0 на Android KitKat, но на нескольких устройствах.
Я создаю файл для вложения следующим образом:
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);
}
Есть ли что-то, что я делаю неправильно при создании файла или отправке намерения? Есть ли лучший способ запустить почтовое приложение со вложением? Альтернативно - кто-то столкнулся с этой проблемой и нашел обходное решение для этого?
Спасибо!
Ответы
Ответ 1
GMail 5.0 добавила некоторые проверки безопасности в приложения, которые он получает от намерения. Они не связаны с разрешениями unix, поэтому факт, что файл доступен для чтения, не имеет значения.
Когда вложение Uri является файлом://, оно будет принимать файлы только из внешнего хранилища, частный каталог самого gmail или файлы, читаемые в мире, из личного каталога данных вызывающего приложения.
Проблема с этой проверкой безопасности заключается в том, что она полагается на то, что gmail может найти приложение-вызывающий, которое является надежным только тогда, когда вызывающий абонент попросил результата. В вашем коде выше вы не запрашиваете результат, и поэтому gmail не знает, кто является вызывающим, и отклоняет ваш файл.
Поскольку он работал для вас в 4.9, но не в 5.0, вы знаете, что это не проблема разрешения unix, поэтому причиной должны быть новые проверки.
TL; DR ответ:
заменить startActivity на startActivityForResult.
Или еще лучше использовать поставщика контента.
Ответ 2
Мне удалось передать файл .jpeg для скриншотов из моего приложения в GMail 5.0 через Intent. Ключ был в этом ответе.
Все, что у меня есть из кода @natasky, почти идентично, но вместо этого у меня есть каталог файлов как
context.getExternalCacheDir();
Какой "представляет собой каталог внешнего хранилища, где вы должны сохранять файлы кеша" (документация)
Ответ 3
Используйте getExternalCacheDir()
с File.createTempFile
.
Для создания файла временного во внешнем кеш-каталоге используйте следующее:
String fileExtension = ".tmp";
File temporaryFile = File.createTempFile( fileName, fileExtension, context.getExternalCacheDir() );
Ответ 4
Я тестировал его, и я узнал, что это определенно проблема с доступом к хранилищу.
Когда вы прикрепляете какой-либо файл к Gmail (более 5.0), не используйте файл из частного хранилища, например /data/data/package/. Попробуйте использовать /storage/sdcard.
Вы можете успешно присоединить свой файл.
Ответ 5
Вы должны реализовать FileProvider, который может создать Uris для ваших внутренних файлов приложения. Другим приложениям разрешено читать этот Uris. Затем просто вместо вызова Uri.fromFile(вложения) вы создаете экземпляр своего FileProvider и используете:
fileProvider.getUriForFile(attachment);
Ответ 6
Не знаю, почему GMail 5.0 не любит определенные пути к файлам (что я подтвердил, что у него есть доступ на чтение), но, по-видимому, лучшим решением является реализация собственного класса ContentProvider для обслуживания файла. Это на самом деле несколько просто, и я нашел здесь достойный пример: http://stephendnicholas.com/archives/974
Обязательно добавьте тег в свой манифест приложения и включите в него "android: grantUriPermissions =" true "". Вы также захотите реализовать getType() и вернуть соответствующий тип MIME для URI файла, иначе некоторые приложения не будут работать с этим... Вот пример этого в разделе комментариев по ссылке.
Ответ 7
У Google есть ответ для этой проблемы:
-
Храните данные в своем собственном ContentProvider
, удостоверяясь, что другие приложения имеют правильное разрешение на доступ к вашему провайдеру. Предпочтительным механизмом обеспечения доступа является использование для разрешений URI, которые являются временными и предоставляют только доступ к приложению-получателю. Простым способом создания ContentProvider
, как это, является использование вспомогательного класса FileProvider
.
-
Используйте систему MediaStore
. MediaStore
в первую очередь ориентирован на типы видео, аудио и изображений MIME, однако начиная с Android 3.0 (уровень API 11) он также может хранить не-медиа-типы (см. MediaStore.Files
для получения дополнительной информации). Файлы можно вставить в MediaStore
с помощью scanFile()
, после чего content://
style Uri
, подходящий для совместного использования, передается в предоставленный onScanCompleted()
обратный вызов. Обратите внимание, что после добавления в систему MediaStore
содержимое доступно для любого приложения на устройстве.
Также вы можете попробовать установить разрешения для своего файла:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
И, наконец, вы можете копировать/хранить ваши файлы во внешнем хранилище - там не нужны разрешения.
Ответ 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[]{"[email protected]"});
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" - растровое изображение, которое было возвращено с помощью намерения камеры