I have some images stored in local app connected with certain contexts (like contacts). I'm using direct share (API 23+) via ChooserTargetService
to show these to choose from and I want to ChooserTarget
instances to have Icon
filled with these images.
So I thought I can use android.support.v4.content.FileProvider
for this (inside ChooserTargetService::onGetChooserTargets
):
val file = File(File(filesDir, "images"), imageFileName)
val contentUri = FileProvider.getUriForFile(this, "com.company.fileprovider", file)
val icon = Icon.createWithContentUri(contentUri)
and in Manifest:
<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>
but the problem is I get an exception
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)
It's not possible to follow "exported" suggestion, becasue FileProvider
unfortunatelly has it hardcoded not to allow it (from FileProvider.java
android support library source code):
// 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");
}
so I tried to call
grantUriPermission("<something goes here>", contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
but it's not obvious what should be put as package name first parameter. From the exception details you can deduct that code is in com.android.internal.app.ChooserActivity
and is called by system.
Edit:
Using Icon.createWithFilePath
is not possible, because you cannot access the file from different process:
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)
and if you try to set file to deprecated Context.MODE_WORLD_READABLE
, you get:
java.lang.SecurityException: MODE_WORLD_READABLE no longer supported
on Andorid 7.
Why not create the Icon's image with the data from the file before you create the ChooserTarget
. This is what Google's Messenger app does.
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);
You could even add some compression to the bitmap if you are so inclined.
However, I do have to warn that you need to be wary of the amount and size of these that you're putting across the Binder...these are Parcelable objects and, as of 7.0, the binder can throw TransacationTooLargeException
if you put too many or too large of Bitmaps into these ChooserTarget
s or any Parcelable sent across it.