通过ChooserTargetService,FileProvider和grantUriPermission为系统选择器提供图标

时间:2017-05-10 14:39:01

标签: android android-support-library android-contentprovider android-intent-chooser

我有一些图像存储在与某些上下文(如联系人)相关的本地应用程序中。我通过ChooserTargetService使用直接共享(API 23+)来显示这些内容以供选择,我希望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)

和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>

但问题是我得到了一个例外

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不幸的是硬编码不允许它(来自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并由系统调用。

修改

无法使用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

on Andorid 7。

2 个答案:

答案 0 :(得分:2)

在创建ChooserTarget之前,为什么不使用文件中的数据创建Icon的图像。这就是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开始,绑定器可以抛出{{ 1}}如果您将过多或过大的位图放入这些TransacationTooLargeException或其中发送的任何Parcelable。

答案 1 :(得分:-2)

MODE_WORLD_READABLE是一个安全漏洞。
因此,谷歌首先弃用它然后完全删除它。
在Android M版本中不推荐使用MODE_WORLD_READABLE。在Android N中,不再支持它并抛出SecurityException。

方案: 请尝试其他模式。我使用了Context.MODE_PRIVATE并且它有效。