0

Next code for deleting a file which my app owns works ok, there is no exception RecoverableSecurityException because the file was created by my app (using ContentResolver.insert(...) method)

getVideoFileContentUri(context, file)?.let { uri ->
    try {
        context.contentResolver.delete(uri, null, null)
    } catch (securityException: RecoverableSecurityException) {
        val intentSender =
            securityException.userAction.actionIntent.intentSender
        intentSender?.let {
            activity.startIntentSenderForRecsult(
                intentSender,
                REQUEST_CODE,
                null,
                0,
                0,
                0,
                null
            )
        }
    }
}

fun getVideoFileContentUri(context: Context, file: File): Uri? {
    val filePath = file.absolutePath
    val cursor = context.contentResolver.query(
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Video.Media._ID),
        MediaStore.Video.Media.DATA + "=? ", arrayOf(filePath), null
    )
    return if (cursor != null && cursor.moveToFirst()) {
        val id: Int = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
        cursor.close()
        Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "" + id)
    } else {
        null
    }
}

But if I update a file created by app using ContentResolver.update(...) method then deleting the file will require permission - it throws RecoverableSecurityException and starts intent which opens a system dialog to confirm modifying the file

// here I change file name of the file
val contentValues = ContentValues(1).apply {
    put(MediaStore.Video.Media.DISPLAY_NAME, "SOME NEW FILE NAME")
}
contentResolver.update(uri, contentValues, null, null)

So now it doesn't look like my app owns that file and for deleting it my users have to confirm deletion for each file

enter image description here

This is really annoying, how can I solve this problem?

So after ContentResolver.update(...) for your own file created by ContentResolver.insert(...) app loses permission for modifying this file and will require requesting it

user924
  • 8,146
  • 7
  • 57
  • 139
  • ContentResolver.update(...) will not update the file but only some info in the mediastore. The file itself will not be touched/changed/rewritten/updated or something like that. You could rephrase your post. You are updating mediastore meta info/data for the file. – blackapps Sep 01 '20 at 13:43
  • @blackapps you aren't right. I create a file with `val uri = resolver.insert(...)` and then record a video to that file by passing `resolver.openFileDescriptor(uri, "rw")` (its field `fileDescriptor`) to `MediaRecorder` instance and when I stop video recording, I rename a file with `ContentResolver.update(...)`, and I see that file name is changed. I viewed files using Explorer app, e.g., this one https://play.google.com/store/apps/details?id=com.speedsoftware.explorer&hl=en – user924 Sep 01 '20 at 13:48
  • @blackapps so using `ContentResolver` you can create, delete and update files and not just in `MediaStore` but on the storage as well, just test it yourself. Read https://developer.android.com/training/data-storage/shared/media#update-item Because for Android Q there is no other way to create or delete media files in public shared media folders, mb using `Documents` API – user924 Sep 01 '20 at 13:54
  • You are now confusing me as your code is for Android below Q and you now talk about Q. Please take one of the two. And most things i did myself but never had your problem so i'm quite interested and want to reproduce it but then i have to know what you do. – blackapps Sep 01 '20 at 14:12
  • @blackapps it's code for Android >= Q, believe me, and `getExternalStoragePublicDirectory` for Android below Q (you can ignore `getVideoFileContentUri` func in my sample, as this method is deprecated, but there is also other solution available to get `Uri` from `File`). Never mind, I found an answer, I will post an answer in my question after some mintes – user924 Sep 01 '20 at 14:14
  • It cannot be for Android Q as you can not use the .DATA column then. – blackapps Sep 01 '20 at 14:14
  • @blackapps you can ignore `getVideoFileContentUri` func in my sample, as this method is deprecated, but there is also other solution available to get `Uri` from `File`, the question isn't about how to get `Uri` from `File`, we can use ` MediaScannerConnection.scanFile(...)` method to get `Uri` from `File` and it's not deprecated – user924 Sep 01 '20 at 14:17
  • @blackapps the question is about `ContentResolver`'s update method (which is ok for Q), but I found a solution already – user924 Sep 01 '20 at 14:18

1 Answers1

0

I found a solution.

When you create a file we need to add put(MediaStore.*.Media.IS_PENDING, 1) to content values:

val contentValues = ContentValues(5).apply {
    put(MediaStore.Video.Media.IS_PENDING, 1)
    put(MediaStore.Video.Media.DISPLAY_NAME, "INITIAL FILENAME")
    put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
    put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
    put(MediaStore.Video.Media.RELATIVE_PATH, relativePath)
}

val uri = resolver.insert(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    contentValues
)

And then when you're done with a file (e.g., finished video recording using MediaRecorder to that file) you can update it like this (you have to add put(MediaStore.*.Media.IS_PENDING, 0):

val contentValues = ContentValues(2).apply {
    put(MediaStore.Video.Media.IS_PENDING, 0)
    put(MediaStore.Video.Media.DISPLAY_NAME, "NEW FILENAME, E.G. APPENDING END RECORDING TIMESTAMP TO PREVIOUS FILE NAME")
}
resolver.update(uri, contentValues, null, null)

And then when calling ContentResolver.delete(...) function, requesting permission is not needed anymore (RecoverableSecurityException isn't thrown)

try {
    resolver.delete(uri, null, null)
} catch (securityException: RecoverableSecurityException) {
    val intentSender =
        securityException.userAction.actionIntent.intentSender
    intentSender?.let {
        activity.startIntentSenderForRecsult(
            intentSender,
            REQUEST_CODE,
            null,
            0,
            0,
            0,
            null
        )
}

Users aren't annoyed :)

Though if a user reinstall your app, permission requesting will be required for all the files your app previously created, yeah beginning with Android Q it's not easy... :)

user924
  • 8,146
  • 7
  • 57
  • 139
  • Of course you use IS_PENDING twice on Android 10+. We all do. But .. yes.. if you dont show your code... – blackapps Sep 01 '20 at 15:57
  • @blackapps what is your problem?) it's not about how to use it, it's about in which cases you may need it. In my case it's helped to resolve losing file permission after calling `ContentResolver.update(..)` method – user924 Sep 01 '20 at 16:45
  • So it's not just about: `While this flag is set, only the owner of the item can open the underlying file; requests from other apps will be rejected.` as it's said in documentation. As we see without using that flag app can lose permission for a file after using some methods – user924 Sep 01 '20 at 16:51
  • That also we know all. No need to tell now. – blackapps Sep 01 '20 at 16:53
  • @blackapps what do we know? You didn't answer my question, but started posting off-topic comments) – user924 Sep 01 '20 at 16:55