Подтвердить что ты не робот

Приложение 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);
}

Есть ли что-то, что я делаю неправильно при создании файла или отправке намерения? Есть ли лучший способ запустить почтовое приложение со вложением? Альтернативно - кто-то столкнулся с этой проблемой и нашел обходное решение для этого?

Спасибо!

4b9b3361

Ответ 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" - растровое изображение, которое было возвращено с помощью намерения камеры