Предоставление значка для выбора системы с помощью ChooserTargetService, FileProvider и grantUriPermission

У меня есть некоторые изображения, хранящиеся в локальном приложении, связанные с определенными контекстами (например, контакты). Я использую прямой share (API 23+) через ChooserTargetService, чтобы показать их на выбор, и я хочу, чтобы ChooserTarget экземпляры имели Icon, заполненные этими изображениями.

Поэтому я подумал, что могу использовать android.support.v4.content.FileProvider для этого (внутри ChooserTargetService::onGetChooserTargets):

val file = File(File(filesDir, "images"), imageFileName)
val contentUri = FileProvider.getUriForFile(this, "com.company.fileprovider", file)
val icon = Icon.createWithContentUri(contentUri)

и в манифесте:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mycompany.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths"/>
</provider>

но проблема в том, что я получаю исключение

05-10 16:06:09.100 32444-32444/android:ui W/Icon: Unable to load image from URI: content://com.mycompany.fileprovider/images/icon_dice.png
java.lang.SecurityException: Permission Denial: reading android.support.v4.content.FileProvider uri content://com.mycompany.fileprovider/images/icon_dice.png from pid=32444, uid=1000 requires the provider be exported, or grantUriPermission()
    at android.os.Parcel.readException(Parcel.java:1684)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
    at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:146)
    at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:692)
    at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1147)
    at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:984)
    at android.content.ContentResolver.openInputStream(ContentResolver.java:704)
    at android.graphics.drawable.Icon.loadDrawableInner(Icon.java:335)
    at android.graphics.drawable.Icon.loadDrawable(Icon.java:272)
    at com.android.internal.app.ChooserActivity$ChooserTargetInfo.<init>(ChooserActivity.java:645)
    at com.android.internal.app.ChooserActivity$ChooserListAdapter.addServiceResults(ChooserActivity.java:1003)
    at com.android.internal.app.ChooserActivity$1.handleMessage(ChooserActivity.java:126)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6119)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

Невозможно следовать "экспортированному" предложению, потому что FileProvider unfortunatelly его жестко запрограммировал, чтобы его не разрешать (из FileProvider.java исходного кода библиотеки поддержки Android):

// Sanity check our security
if (info.exported) {
    throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
    throw new SecurityException("Provider must grant uri permissions");
}

поэтому я попытался позвонить

grantUriPermission("<something goes here>", contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)

но не очевидно, какой должен быть первый параметр имени пакета. Из деталей исключения вы можете вычесть, что код находится в com.android.internal.app.ChooserActivity и вызывается системой.

Edit:

Использование Icon.createWithFilePath невозможно, потому что вы не можете получить доступ к файлу из другого процесса:

W/Icon: Unable to load image from path: /data/user/0/com.mycompany.app/files/images/image.png
java.io.FileNotFoundException: /data/user/0/com.mycompany.app/files/images/image.png (Permission denied)

и если вы попытаетесь установить файл на устаревший Context.MODE_WORLD_READABLE, вы получите:

java.lang.SecurityException: MODE_WORLD_READABLE no longer supported

на Андориде 7.

Ответ 1

Почему бы не создать образ Icon с данными из файла перед созданием ChooserTarget. Это то, что делает приложение Google Messenger.

File file = new File(new File(filesDir, "images"), imageFileName);
Bitmap b = BitmapFactory.decodeStream(new FileInputStream(file));
Icon icon = Icon.createWithBitmap(b);
return new ChooserTarget(title, icon, score, cn, extras);

Вы даже можете добавить некоторое сжатие в растровое изображение, если вы так склонны.

Тем не менее, я должен предупредить, что вам нужно быть осторожным в отношении количества и размера этих данных, которые вы переносите через Binder... это объекты Parcelable, и с 7.0 связующее может бросить TransacationTooLargeException если вы помещаете слишком много или слишком больших битмапов в эти ChooserTarget или любые Parcelable, отправленные через него.

Ответ 2

MODE_WORLD_READABLE - это недостаток безопасности.
Так, Google сначала отказался от этого, а затем полностью удалил его.
MODE_WORLD_READABLE устарел в версиях до Android M. И в Android N он больше не поддерживается и бросает SecurityException.

Решение: Пожалуйста, попробуйте другой режим. Я использовал Context.MODE_PRIVATE и работал.