yasirkula / UnityNativeShare

A Unity plugin to natively share files (images, videos, documents, etc.) and/or plain text on Android & iOS
MIT License
891 stars 131 forks source link

java.lang.SecurityException: Permission Denial #91

Closed dthul closed 3 years ago

dthul commented 3 years ago

Description of the bug

We switched to Android 10 / API Level 29 and had to adjust our file creation code to support the new scoped storage (which will become mandatory with Android 11). Before, we just did:

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "MyApp" + File.separator + "2020-09-21 20-11-48.mp4");

whereas now we do something more elaborate:

ContentResolver resolver = context.getContentResolver();
String relativeLocation = Environment.DIRECTORY_MOVIES + File.separator + "MyApp";
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Video.VideoColumns.DISPLAY_NAME, "2020-09-21 20-11-48.mp4");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
contentValues.put(MediaStore.Video.VideoColumns.RELATIVE_PATH, relativeLocation);
Uri uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues);

This will generate a content Uri like "content://...". Since UnityNativeShare doesn't understand those, I convert the Uri to a file path with the following code:

try (final Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DATA}, null, null, null)) {
    if (cursor != null && cursor.moveToFirst()) {
        final int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
        return cursor.getString(column_index);
    }
}

When trying to share such a Uri derived path, I get the following error:

2020-09-21 20:11:55.159 8617-8641/com.myapp E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading com.yasirkula.unity.NativeShareContentProvider uri content://com.myapp.NativeShareContentProvider/devroot/storage/emulated/0/Movies/MyApp/2020-09-21%2020-11-48.mp4 from pid=9310, uid=1000 requires the provider be exported, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:740)
        at android.content.ContentProvider.semEnforceReadPermission(ContentProvider.java:659)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:602)
        at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:593)
        at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:507)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:307)
        at android.os.Binder.execTransactInternal(Binder.java:1056)
        at android.os.Binder.execTransact(Binder.java:1029)

The share is unsuccessful and the receiving app reports an error (for example WhatsApp says "The file format is not supported", while Gmail complains that it is "Unable to attach file.") I saw that there are two closed issues (#61, #85) which reported the same error but said that the share was successful. This is not the case for me.

Platform specs

Please provide the following info if this is a Unity 3D repository.

yasirkula commented 3 years ago

It is not possible to access files outside Application.persistentDataPath and Application.temporaryCachePath on Storage Access Framework. Even though you have a filepath, it is simply inaccessible. You must copy the file to one of the aforementioned locations to be able to share it.

dthul commented 3 years ago

Thanks for your reply! I am able to share the file using the following code which does not require any file path and works solely with the Uri from the media store:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("video/mp4");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, null));

I wonder if this is something that UnityNativeShare would be able to support, or if we need to use our own sharing code similar to the one above.

yasirkula commented 3 years ago

I'm not planning to add raw Uri support to NativeShare because it is Android only and is a very specific requirement (it hasn't been needed before). You can modify NativeShare's source code to add this functionality or use your own sharing code as you've mentioned.

yasirkula commented 3 years ago

P.S. On Android 11, filepaths for media files will likely be accessible via File APIs again: https://developer.android.com/training/data-storage/shared/media#direct-file-paths. So setting requestLegacyExternalStorage to true in AndroidManifest (as explained in the documentation) might be another solution. Bu you'll need READ_EXTERNAL_STORAGE permission granted.

dthul commented 3 years ago

Thanks for all the hints! We'll decide whether to enable legacy external storage for Android 10 or go with the Uri approach.