flutter_downloader icon indicating copy to clipboard operation
flutter_downloader copied to clipboard

[android] Exception when removing a file from application's files directory

Open mr-mmmmore opened this issue 3 years ago • 5 comments

I store the downloaded files in the application's files directory (using getApplicationDocumentsDirectory() of the path_provider plugin). It works ok excepted when trying to remove the file on Android with the remove method, I get the following exception:

E/MethodChannel#vn.hunghd/downloader(25411): Failed to handle method call
E/MethodChannel#vn.hunghd/downloader(25411): java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider
    uri content://media/external/images/media from pid=25411, uid=10138 requires android.permission.READ_EXTERNAL_STORAGE,
    or grantUriPermission()
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.os.Parcel.readException(Parcel.java:1954)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.content.ContentResolver.query(ContentResolver.java:766)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.content.ContentResolver.query(ContentResolver.java:716)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.content.ContentResolver.query(ContentResolver.java:667)
E/MethodChannel#vn.hunghd/downloader(25411): 	at vn.hunghd.flutterdownloader.FlutterDownloaderPlugin.deleteFileInMediaStore(FlutterDownloaderPlugin.java:362)
E/MethodChannel#vn.hunghd/downloader(25411): 	at vn.hunghd.flutterdownloader.FlutterDownloaderPlugin.remove(FlutterDownloaderPlugin.java:332)
E/MethodChannel#vn.hunghd/downloader(25411): 	at vn.hunghd.flutterdownloader.FlutterDownloaderPlugin.onMethodCall(FlutterDownloaderPlugin.java:100)
E/MethodChannel#vn.hunghd/downloader(25411): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/MethodChannel#vn.hunghd/downloader(25411): 	at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/MethodChannel#vn.hunghd/downloader(25411): 	at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:692)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.os.MessageQueue.next(MessageQueue.java:379)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.os.Looper.loop(Looper.java:144)
E/MethodChannel#vn.hunghd/downloader(25411): 	at android.app.ActivityThread.main(ActivityThread.java:7529)
E/MethodChannel#vn.hunghd/downloader(25411): 	at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#vn.hunghd/downloader(25411): 	at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
E/MethodChannel#vn.hunghd/downloader(25411): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

So, the android.permission.READ_EXTERNAL_STORAGE is required also I don't use it... The problem comes from the FlutterDownloaderPlugin.deleteFileInMediaStore, on this line:

Cursor imageCursor = contentResolver.query(imageQueryUri, projection, imageSelection, selectionArgs, null);

The method makes the assumption that the file must be either in the "image store" or in the "video store", which causes the error when it's not. This is weird to make this assumption when removing the file while such constraint doesn't exist when creating it. I am not an Android developer so maybe there's a reason that I don't grasp.

However, I have made the following edits and it works now (the files are deleted with no errors):

  1. include the code in a try/catch block to avoid the exception
  2. As a last resort attempt to delete the file using File.delete():
try {
    // search the file in image store first
    Cursor imageCursor = contentResolver.query(imageQueryUri, projection, imageSelection, selectionArgs, null);
    if (imageCursor != null && imageCursor.moveToFirst()) {
        // We found the ID. Deleting the item via the content provider will also remove the file
        long id = imageCursor.getLong(imageCursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
        Uri deleteUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
        contentResolver.delete(deleteUri, null, null);
    } else {
        // File not found in image store DB, try to search in video store
        Cursor videoCursor = contentResolver.query(imageQueryUri, projection, imageSelection, selectionArgs, null);
        if (videoCursor != null && videoCursor.moveToFirst()) {
            // We found the ID. Deleting the item via the content provider will also remove the file
            long id = videoCursor.getLong(videoCursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
            Uri deleteUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
            contentResolver.delete(deleteUri, null, null);
        } else {
            // can not find the file in media store DB at all -> do a simple delete
            file.delete(); // <=================== FILES GET SUCCESSFULLY DELETED HERE
        }
        if (videoCursor != null) videoCursor.close();
    }
    if (imageCursor != null) imageCursor.close();
} catch (Exception e) {
    // fail silently
}

I can submit a PR if there's a chance it gets merged soon.

I don't understand the why of this ContentResolver approach when a simple File.delete() does seem to do the job, but as I said I am not an Android developer. I'd be glad if someone could explain.

Note: I think the deletion from the video store has also a problem, as it uses the same settings as for the image store. On this line:

Cursor videoCursor = contentResolver.query(imageQueryUri, projection, imageSelection, selectionArgs, null);

shouldn't videoQueryUri and videoSelection be used instead of imageQueryUri and imageSelection?

mr-mmmmore avatar Feb 26 '21 11:02 mr-mmmmore

I've found this too, In our application we're aiming to save these files to the app storage however it attempts to remove downloaded files from external storage. Doesn't seem like you can really specify where to remove from - this PR would really help!

Just note that this error will still be thrown and you probably need to check to see if the device has permissions to access the external storage first before trying to query if the items exists in external storage.

danybuoy avatar Mar 05 '21 02:03 danybuoy

Just note that this error will still be thrown and you probably need to check to see if the device has permissions to access the external storage first before trying to query if the items exists in external storage.

This is not what I have experienced: the exception was gone either by granting this permission to the app, or by adding the try/catch. Why do you think the error will still be thrown despite the try/catch?

mr-mmmmore avatar Mar 05 '21 06:03 mr-mmmmore

This commit introduced the code about removing the file from "media store DB" https://github.com/fluttercommunity/flutter_downloader/commit/49332f612ac0c7ee16aa0e50e8ceae1c0941ed2c

@hnvn any idea why this was necessary?

alhafoudh avatar May 10 '21 13:05 alhafoudh

I have prepared fix here for this issue. https://github.com/alhafoudh/flutter_downloader/tree/fix-permission-denied Please test it out. I can submit this as PR afterwards.

alhafoudh avatar May 10 '21 14:05 alhafoudh

any update on the latest release?

P-B1101 avatar Apr 17 '22 07:04 P-B1101

any news here?

felipecastrosales avatar Feb 15 '23 20:02 felipecastrosales

any news here?

I fixed in in #871 :)

bartekpacia avatar Jul 30 '23 21:07 bartekpacia