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.
ContentProvider
, one with read-only support for your files and can be exported. Your proposed hack strikes me as one of those things that might work on some devices but not others, based on Android changes, device manufacturer changes, custom ROM changes, etc. Eventually, I plan to add this feature to myStreamProvider
. IMHO,ChooserTargetService
should be handling the permission stuff for you. – SackenStreamProvider
. What I tried at first wasandroid:exported="true"
+android:readPermission="android.permission.BIND_CHOOSER_TARGET_SERVICE"
(same permission that is required for clients of myChooserTargetService
implementation), which should be something that simply works and is secure if not that hardcoded ifs inFileProvider
. Is it really the case that this hack can be broken? The app chooser / direct share handler is part of system and cannot be replaced as easily as e.g. default launcher app. – LakyIcon
from theChooserTargetService
. You're welcome to trycom.android.systemui
for the package, as that works for notification sounds. Or, if the files are on external storage, useUri.fromFile()
andStrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
to undo theFileUriExposedException
check. – SackenFileProvider
work as exported: gist.github.com/mg6maciej/598b09193ee4bcfa5ba1fd355692dc3a – LakyStreamProvider
feature that I mentioned earlier. – Sacken