Providing icon to system chooser via ChooserTargetService, FileProvider and grantUriPermission
C

2

13

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.

Coltun answered 10/5, 2017 at 14:39 Comment(6)
IMHO, it would be better to use a different 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 my StreamProvider. IMHO, ChooserTargetService should be handling the permission stuff for you.Sacken
@Sacken Thanks. I'll likely use your StreamProvider. What I tried at first was android:exported="true" + android:readPermission="android.permission.BIND_CHOOSER_TARGET_SERVICE" (same permission that is required for clients of my ChooserTargetService implementation), which should be something that simply works and is secure if not that hardcoded ifs in FileProvider. 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.Laky
I cannot rule out the possibility that somebody changes the application ID of the part of the system that consumes the Icon from the ChooserTargetService. You're welcome to try com.android.systemui for the package, as that works for notification sounds. Or, if the files are on external storage, use Uri.fromFile() and StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build()); to undo the FileUriExposedException check.Sacken
Related, but not exactly the same: #37861374Laky
!!! Do NOT do this !!! Just a funny hack to make FileProvider work as exported: gist.github.com/mg6maciej/598b09193ee4bcfa5ba1fd355692dc3aLaky
FWIW, I added the StreamProvider feature that I mentioned earlier.Sacken
H
2

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 ChooserTargets or any Parcelable sent across it.

Heilman answered 18/5, 2017 at 15:26 Comment(3)
This won't work. I added information why it won't to the question at the end.Laky
Updated answer.Heilman
Pulling APKs and decompiling them to source :-)Heilman
C
-2

MODE_WORLD_READABLE is a security flaw.
So, google first deprecated it and then completely removed it.
MODE_WORLD_READABLE was deprecated in versions till Android M. And in Android N it is no longer supported and throws SecurityException.

Solution : Please try a different mode. I used Context.MODE_PRIVATE and it worked.

Coletta answered 19/5, 2017 at 11:5 Comment(1)
The OP's question is about cross-process file sharing...Context.MODE_PRIVATE is not going to work.Heilman

© 2022 - 2024 — McMap. All rights reserved.